Merge "Add @IntRange to MediaMetadataRetriever#getScaledFrameAtTime"
diff --git a/Android.bp b/Android.bp
index a10f8de..93b37f2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -262,12 +262,13 @@
     name: "framework-updatable-sources",
     srcs: [
         ":framework-appsearch-sources",
-        ":framework-sdkext-sources",
+        ":framework-sdkextensions-sources",
         ":framework-statsd-sources",
         ":framework-tethering-srcs",
         ":updatable-media-srcs",
         ":framework-mediaprovider-sources",
         ":framework-wifi-updatable-sources",
+        ":ike-srcs",
     ]
 }
 
@@ -432,6 +433,7 @@
         // TODO(b/146167933): Use framework-statsd-stubs
         "framework-statsd",
         "framework-wifi-stubs",
+        "ike-stubs",
     ],
     installable: true,
     javac_shard_size: 150,
@@ -481,11 +483,12 @@
         "updatable_media_stubs",
         "framework_mediaprovider_stubs",
         "framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs
-        "framework-sdkext-stubs-systemapi",
+        "framework-sdkextensions-stubs-systemapi",
         // TODO(b/146167933): Use framework-statsd-stubs instead.
         "framework-statsd",
         // TODO(b/140299412): should be framework-wifi-stubs
         "framework-wifi",
+        "ike-stubs",
         // TODO(jiyong): add more stubs for APEXes here
     ],
     sdk_version: "core_platform",
@@ -642,13 +645,13 @@
     name: "framework-tethering-shared-srcs",
     srcs: [
         "core/java/android/util/LocalLog.java",
-        "core/java/com/android/internal/util/BitUtils.java",
         "core/java/com/android/internal/util/IndentingPrintWriter.java",
         "core/java/com/android/internal/util/IState.java",
         "core/java/com/android/internal/util/MessageUtils.java",
         "core/java/com/android/internal/util/Preconditions.java",
         "core/java/com/android/internal/util/State.java",
         "core/java/com/android/internal/util/StateMachine.java",
+        "core/java/android/net/shared/Inet4AddressUtils.java",
     ],
 }
 
diff --git a/apex/sdkext/TEST_MAPPING b/apex/sdkext/TEST_MAPPING
deleted file mode 100644
index 91947f3..0000000
--- a/apex/sdkext/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "CtsSdkExtTestCases"
-    }
-  ]
-}
diff --git a/apex/sdkext/framework/Android.bp b/apex/sdkext/framework/Android.bp
deleted file mode 100644
index a50dc3d..0000000
--- a/apex/sdkext/framework/Android.bp
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright (C) 2019 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 {
-    default_visibility: [ ":__pkg__" ]
-}
-
-filegroup {
-    name: "framework-sdkext-sources",
-    srcs: [
-        "java/**/*.java",
-    ],
-    path: "java",
-    visibility: [ "//frameworks/base:__pkg__" ] // For the "global" stubs.
-}
-
-java_library {
-    name: "framework-sdkext",
-    srcs: [ ":framework-sdkext-sources" ],
-    sdk_version: "system_current",
-    libs: [ "framework-annotations-lib" ],
-    permitted_packages: [ "android.os.ext" ],
-    installable: true,
-    visibility: [ "//frameworks/base/apex/sdkext:__pkg__" ],
-}
-
-droidstubs {
-    name: "framework-sdkext-droidstubs-publicapi",
-    defaults: [
-        "framework-sdkext-stubs-defaults",
-        "framework-module-stubs-defaults-publicapi",
-    ]
-}
-
-droidstubs {
-    name: "framework-sdkext-droidstubs-systemapi",
-    defaults: [
-        "framework-sdkext-stubs-defaults",
-        "framework-module-stubs-defaults-systemapi",
-    ]
-}
-
-stubs_defaults {
-    name: "framework-sdkext-stubs-defaults",
-    srcs: [
-        ":framework-sdkext-sources",
-        ":framework-annotations",
-    ],
-    sdk_version: "system_current",
-}
-
-java_library {
-    name: "framework-sdkext-stubs-systemapi",
-    srcs: [":framework-sdkext-droidstubs-systemapi"],
-    sdk_version: "system_current",
-    visibility: [
-      "//frameworks/base:__pkg__", // Framework
-      "//frameworks/base/apex/sdkext:__pkg__", // sdkext SDK
-    ]
-}
diff --git a/apex/sdkext/Android.bp b/apex/sdkextensions/Android.bp
similarity index 83%
rename from apex/sdkext/Android.bp
rename to apex/sdkextensions/Android.bp
index f62f167..4c5c2b2 100644
--- a/apex/sdkext/Android.bp
+++ b/apex/sdkextensions/Android.bp
@@ -18,21 +18,26 @@
 
 apex {
     name: "com.android.sdkext",
-    manifest: "manifest.json",
+    defaults: [ "com.android.sdkext-defaults" ],
     binaries: [ "derive_sdk" ],
-    java_libs: [ "framework-sdkext" ],
+    prebuilts: [ "cur_sdkinfo" ],
+    manifest: "manifest.json",
+}
+
+apex_defaults {
+    name: "com.android.sdkext-defaults",
+    java_libs: [ "framework-sdkextensions" ],
     prebuilts: [
-      "com.android.sdkext.ldconfig",
-      "cur_sdkinfo",
-      "derive_sdk.rc",
+        "com.android.sdkext.ldconfig",
+        "derive_sdk.rc",
     ],
     key: "com.android.sdkext.key",
     certificate: ":com.android.sdkext.certificate",
 }
 
 sdk {
-    name: "sdkext-sdk",
-    java_header_libs: [ "framework-sdkext-stubs-systemapi" ],
+    name: "sdkextensions-sdk",
+    java_header_libs: [ "framework-sdkextensions-stubs-systemapi" ],
 }
 
 apex_key {
diff --git a/apex/sdkext/OWNERS b/apex/sdkextensions/OWNERS
similarity index 100%
rename from apex/sdkext/OWNERS
rename to apex/sdkextensions/OWNERS
diff --git a/apex/sdkextensions/TEST_MAPPING b/apex/sdkextensions/TEST_MAPPING
new file mode 100644
index 0000000..7e77623
--- /dev/null
+++ b/apex/sdkextensions/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSdkExtTestCases"
+    },
+    {
+      "name": "apiextensions_e2e_tests"
+    }
+  ]
+}
diff --git a/apex/sdkext/com.android.sdkext.avbpubkey b/apex/sdkextensions/com.android.sdkext.avbpubkey
similarity index 100%
rename from apex/sdkext/com.android.sdkext.avbpubkey
rename to apex/sdkextensions/com.android.sdkext.avbpubkey
Binary files differ
diff --git a/apex/sdkext/com.android.sdkext.pem b/apex/sdkextensions/com.android.sdkext.pem
similarity index 100%
rename from apex/sdkext/com.android.sdkext.pem
rename to apex/sdkextensions/com.android.sdkext.pem
diff --git a/apex/sdkext/com.android.sdkext.pk8 b/apex/sdkextensions/com.android.sdkext.pk8
similarity index 100%
rename from apex/sdkext/com.android.sdkext.pk8
rename to apex/sdkextensions/com.android.sdkext.pk8
Binary files differ
diff --git a/apex/sdkext/com.android.sdkext.x509.pem b/apex/sdkextensions/com.android.sdkext.x509.pem
similarity index 100%
rename from apex/sdkext/com.android.sdkext.x509.pem
rename to apex/sdkextensions/com.android.sdkext.x509.pem
diff --git a/apex/sdkext/derive_sdk/Android.bp b/apex/sdkextensions/derive_sdk/Android.bp
similarity index 63%
rename from apex/sdkext/derive_sdk/Android.bp
rename to apex/sdkextensions/derive_sdk/Android.bp
index c4e3c29..cf49902 100644
--- a/apex/sdkext/derive_sdk/Android.bp
+++ b/apex/sdkextensions/derive_sdk/Android.bp
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-cc_binary {
-    name: "derive_sdk",
+cc_defaults {
+    name: "derive_sdk-defaults",
     srcs: [
         "derive_sdk.cpp",
         "sdk.proto",
@@ -30,6 +30,24 @@
     ],
 }
 
+cc_binary {
+    name: "derive_sdk",
+    defaults: [ "derive_sdk-defaults" ],
+    apex_available: [ "com.android.sdkext" ],
+    visibility: [ "//frameworks/base/apex/sdkextensions" ]
+}
+
+// Work around testing using a 64-bit test suite on 32-bit test device by
+// using a prefer32 version of derive_sdk in testing.
+cc_binary {
+    name: "derive_sdk_prefer32",
+    defaults: [ "derive_sdk-defaults" ],
+    compile_multilib: "prefer32",
+    stem: "derive_sdk",
+    apex_available: [ "test_com.android.sdkext" ],
+    visibility: [ "//frameworks/base/apex/sdkextensions/testing" ]
+}
+
 prebuilt_etc {
     name: "derive_sdk.rc",
     src: "derive_sdk.rc",
diff --git a/apex/sdkext/derive_sdk/derive_sdk.cpp b/apex/sdkextensions/derive_sdk/derive_sdk.cpp
similarity index 97%
rename from apex/sdkext/derive_sdk/derive_sdk.cpp
rename to apex/sdkextensions/derive_sdk/derive_sdk.cpp
index 0a97116..6fb7ef4 100644
--- a/apex/sdkext/derive_sdk/derive_sdk.cpp
+++ b/apex/sdkextensions/derive_sdk/derive_sdk.cpp
@@ -26,7 +26,7 @@
 #include <android-base/logging.h>
 #include <android-base/properties.h>
 
-#include "frameworks/base/apex/sdkext/derive_sdk/sdk.pb.h"
+#include "frameworks/base/apex/sdkextensions/derive_sdk/sdk.pb.h"
 
 using com::android::sdkext::proto::SdkVersion;
 
diff --git a/apex/sdkext/derive_sdk/derive_sdk.rc b/apex/sdkextensions/derive_sdk/derive_sdk.rc
similarity index 100%
rename from apex/sdkext/derive_sdk/derive_sdk.rc
rename to apex/sdkextensions/derive_sdk/derive_sdk.rc
diff --git a/apex/sdkext/derive_sdk/sdk.proto b/apex/sdkextensions/derive_sdk/sdk.proto
similarity index 100%
rename from apex/sdkext/derive_sdk/sdk.proto
rename to apex/sdkextensions/derive_sdk/sdk.proto
diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp
new file mode 100644
index 0000000..5504f4e
--- /dev/null
+++ b/apex/sdkextensions/framework/Android.bp
@@ -0,0 +1,74 @@
+// Copyright (C) 2019 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 {
+    default_visibility: [ ":__pkg__" ]
+}
+
+filegroup {
+    name: "framework-sdkextensions-sources",
+    srcs: [
+        "java/**/*.java",
+    ],
+    path: "java",
+    visibility: [ "//frameworks/base" ] // For the "global" stubs.
+}
+
+java_library {
+    name: "framework-sdkextensions",
+    srcs: [ ":framework-sdkextensions-sources" ],
+    sdk_version: "system_current",
+    libs: [ "framework-annotations-lib" ],
+    permitted_packages: [ "android.os.ext" ],
+    installable: true,
+    visibility: [
+        "//frameworks/base/apex/sdkextensions",
+        "//frameworks/base/apex/sdkextensions/testing",
+    ],
+}
+
+droidstubs {
+    name: "framework-sdkextensions-droidstubs-publicapi",
+    defaults: [
+        "framework-sdkextensions-stubs-defaults",
+        "framework-module-stubs-defaults-publicapi",
+    ]
+}
+
+droidstubs {
+    name: "framework-sdkextensions-droidstubs-systemapi",
+    defaults: [
+        "framework-sdkextensions-stubs-defaults",
+        "framework-module-stubs-defaults-systemapi",
+    ]
+}
+
+stubs_defaults {
+    name: "framework-sdkextensions-stubs-defaults",
+    srcs: [
+        ":framework-sdkextensions-sources",
+        ":framework-annotations",
+    ],
+    sdk_version: "system_current",
+}
+
+java_library {
+    name: "framework-sdkextensions-stubs-systemapi",
+    srcs: [":framework-sdkextensions-droidstubs-systemapi"],
+    sdk_version: "system_current",
+    visibility: [
+        "//frameworks/base", // Framework
+        "//frameworks/base/apex/sdkextensions", // sdkextensions SDK
+    ]
+}
diff --git a/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java
similarity index 100%
rename from apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
rename to apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java
diff --git a/apex/sdkext/framework/java/android/os/ext/package.html b/apex/sdkextensions/framework/java/android/os/ext/package.html
similarity index 100%
rename from apex/sdkext/framework/java/android/os/ext/package.html
rename to apex/sdkextensions/framework/java/android/os/ext/package.html
diff --git a/apex/sdkext/gen_sdkinfo.py b/apex/sdkextensions/gen_sdkinfo.py
similarity index 100%
rename from apex/sdkext/gen_sdkinfo.py
rename to apex/sdkextensions/gen_sdkinfo.py
diff --git a/apex/sdkext/ld.config.txt b/apex/sdkextensions/ld.config.txt
similarity index 91%
rename from apex/sdkext/ld.config.txt
rename to apex/sdkextensions/ld.config.txt
index b447068..dcc69b8 100644
--- a/apex/sdkext/ld.config.txt
+++ b/apex/sdkextensions/ld.config.txt
@@ -1,10 +1,10 @@
 # Copyright (C) 2019 The Android Open Source Project
 #
-# Bionic loader config file for the sdkext apex.
+# Bionic loader config file for the sdkextensions apex.
 
-dir.sdkext = /apex/com.android.sdkext/bin/
+dir.sdkextensions = /apex/com.android.sdkext/bin/
 
-[sdkext]
+[sdkextensions]
 additional.namespaces = platform
 
 namespace.default.isolated = true
diff --git a/apex/sdkext/manifest.json b/apex/sdkextensions/manifest.json
similarity index 100%
rename from apex/sdkext/manifest.json
rename to apex/sdkextensions/manifest.json
diff --git a/apex/sdkext/sdk.proto b/apex/sdkextensions/sdk.proto
similarity index 100%
rename from apex/sdkext/sdk.proto
rename to apex/sdkextensions/sdk.proto
diff --git a/apex/sdkextensions/testing/Android.bp b/apex/sdkextensions/testing/Android.bp
new file mode 100644
index 0000000..e6451cc
--- /dev/null
+++ b/apex/sdkextensions/testing/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2019 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.
+
+apex {
+    name: "test_com.android.sdkext",
+    visibility: [ "//system/apex/tests" ],
+    defaults: ["com.android.sdkext-defaults"],
+    manifest: "test_manifest.json",
+    prebuilts: [ "sdkinfo_45" ],
+    file_contexts: ":com.android.sdkext-file_contexts",
+    installable: false, // Should never be installed on the systemimage
+    multilib: {
+        prefer32: {
+            binaries: ["derive_sdk_prefer32"],
+        },
+    },
+    // The automated test infra ends up building this apex for 64+32-bit and
+    // then installs it on a 32-bit-only device. Work around this weirdness
+    // by preferring 32-bit.
+    compile_multilib: "prefer32",
+}
+
+genrule {
+    name: "sdkinfo_45_src",
+    out: [ "sdkinfo.binarypb" ],
+    tools: [ "gen_sdkinfo" ],
+    cmd: "$(location) -v 45 -o $(out)",
+}
+
+prebuilt_etc {
+    name: "sdkinfo_45",
+    src: ":sdkinfo_45_src",
+    filename: "sdkinfo.binarypb",
+    installable: false,
+}
diff --git a/apex/sdkextensions/testing/test_manifest.json b/apex/sdkextensions/testing/test_manifest.json
new file mode 100644
index 0000000..1b4a2b0
--- /dev/null
+++ b/apex/sdkextensions/testing/test_manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.sdkext",
+  "version": 2147483647
+}
diff --git a/apex/statsd/aidl/android/os/IStatsManagerService.aidl b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
index 05583677..2a3665c 100644
--- a/apex/statsd/aidl/android/os/IStatsManagerService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
@@ -85,4 +85,26 @@
      * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
      */
     void unsetBroadcastSubscriber(long configKey, long subscriberId, in String packageName);
+
+    /**
+     * Returns the most recently registered experiment IDs.
+     *
+     * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+     */
+    long[] getRegisteredExperimentIds();
+
+    /**
+     * Fetches metadata across statsd. Returns byte array representing wire-encoded proto.
+     *
+     * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+     */
+    byte[] getMetadata(in String packageName);
+
+    /**
+     * Fetches data for the specified configuration key. Returns a byte array representing proto
+     * wire-encoded of ConfigMetricsReportList.
+     *
+     * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+     */
+    byte[] getData(in long key, in String packageName);
 }
\ No newline at end of file
diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl
index 9358439..c08abdd 100644
--- a/apex/statsd/aidl/android/os/IStatsd.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -89,14 +89,14 @@
      *
      * Requires Manifest.permission.DUMP.
      */
-    byte[] getData(in long key, in String packageName);
+    byte[] getData(in long key, int callingUid);
 
     /**
      * Fetches metadata across statsd. Returns byte array representing wire-encoded proto.
      *
      * Requires Manifest.permission.DUMP.
      */
-    byte[] getMetadata(in String packageName);
+    byte[] getMetadata();
 
     /**
      * Sets a configuration with the specified config key and subscribes to updates for this
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
index 1d03e3b..eff9bda 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
@@ -19,6 +19,7 @@
 import static com.android.server.stats.StatsCompanion.PendingIntentRef;
 
 import android.Manifest;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -245,20 +246,85 @@
         }
     }
 
+    @Override
+    public long[] getRegisteredExperimentIds() throws IllegalStateException {
+        enforceDumpAndUsageStatsPermission(null);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            IStatsd statsd = waitForStatsd();
+            if (statsd != null) {
+                return statsd.getRegisteredExperimentIds();
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to getRegisteredExperimentIds with statsd");
+            throw new IllegalStateException(e.getMessage(), e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        throw new IllegalStateException("Failed to connect to statsd to registerExperimentIds");
+    }
+
+    @Override
+    public byte[] getMetadata(String packageName) throws IllegalStateException {
+        enforceDumpAndUsageStatsPermission(packageName);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            IStatsd statsd = waitForStatsd();
+            if (statsd != null) {
+                return statsd.getMetadata();
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to getMetadata with statsd");
+            throw new IllegalStateException(e.getMessage(), e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        throw new IllegalStateException("Failed to connect to statsd to getMetadata");
+    }
+
+    @Override
+    public byte[] getData(long key, String packageName) throws IllegalStateException {
+        enforceDumpAndUsageStatsPermission(packageName);
+        int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            IStatsd statsd = waitForStatsd();
+            if (statsd != null) {
+                return statsd.getData(key, callingUid);
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to getData with statsd");
+            throw new IllegalStateException(e.getMessage(), e);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        throw new IllegalStateException("Failed to connect to statsd to getData");
+    }
+
     void setStatsCompanionService(StatsCompanionService statsCompanionService) {
         mStatsCompanionService = statsCompanionService;
     }
 
-    private void enforceDumpAndUsageStatsPermission(String packageName) {
+    /**
+     * Checks that the caller has both DUMP and PACKAGE_USAGE_STATS permissions. Also checks that
+     * the caller has USAGE_STATS_PERMISSION_OPS for the specified packageName if it is not null.
+     *
+     * @param packageName The packageName to check USAGE_STATS_PERMISSION_OPS.
+     */
+    private void enforceDumpAndUsageStatsPermission(@Nullable String packageName) {
         int callingUid = Binder.getCallingUid();
         int callingPid = Binder.getCallingPid();
 
         if (callingPid == Process.myPid()) {
             return;
         }
+
         mContext.enforceCallingPermission(Manifest.permission.DUMP, null);
         mContext.enforceCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS, null);
 
+        if (packageName == null) {
+            return;
+        }
         AppOpsManager appOpsManager = (AppOpsManager) mContext
                 .getSystemService(Context.APP_OPS_SERVICE);
         switch (appOpsManager.noteOp(USAGE_STATS_PERMISSION_OPS,
diff --git a/api/current.txt b/api/current.txt
index e98dccf..65a4217 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6790,6 +6790,7 @@
     method @Nullable public java.util.List<java.lang.String> getPermittedAccessibilityServices(@NonNull android.content.ComponentName);
     method @Nullable public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName);
     method @Nullable public java.util.List<java.lang.String> getPermittedInputMethods(@NonNull android.content.ComponentName);
+    method @NonNull public java.util.List<java.lang.String> getProtectedPackages(@NonNull android.content.ComponentName);
     method public long getRequiredStrongAuthTimeout(@Nullable android.content.ComponentName);
     method public boolean getScreenCaptureDisabled(@Nullable android.content.ComponentName);
     method public java.util.List<android.os.UserHandle> getSecondaryUsers(@NonNull android.content.ComponentName);
@@ -6911,6 +6912,7 @@
     method public boolean setPermittedInputMethods(@NonNull android.content.ComponentName, java.util.List<java.lang.String>);
     method public void setProfileEnabled(@NonNull android.content.ComponentName);
     method public void setProfileName(@NonNull android.content.ComponentName, String);
+    method public void setProtectedPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
     method public void setRecommendedGlobalProxy(@NonNull android.content.ComponentName, @Nullable android.net.ProxyInfo);
     method public void setRequiredStrongAuthTimeout(@NonNull android.content.ComponentName, long);
     method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
@@ -28679,7 +28681,9 @@
     method public int getVideoHeight();
     method public float getVideoPixelAspectRatio();
     method public int getVideoWidth();
+    method public boolean isAudioDescription();
     method public boolean isEncrypted();
+    method public boolean isHardOfHearing();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TvTrackInfo> CREATOR;
     field public static final int TYPE_AUDIO = 0; // 0x0
@@ -28691,10 +28695,12 @@
     ctor public TvTrackInfo.Builder(int, @NonNull String);
     method public android.media.tv.TvTrackInfo build();
     method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioDescription(boolean);
     method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
     method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence);
     method @NonNull public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
     method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setHardOfHearing(boolean);
     method public android.media.tv.TvTrackInfo.Builder setLanguage(String);
     method public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
     method public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float);
@@ -29477,6 +29483,7 @@
     method public android.net.NetworkRequest.Builder addCapability(int);
     method public android.net.NetworkRequest.Builder addTransportType(int);
     method public android.net.NetworkRequest build();
+    method @NonNull public android.net.NetworkRequest.Builder clearCapabilities();
     method public android.net.NetworkRequest.Builder removeCapability(int);
     method public android.net.NetworkRequest.Builder removeTransportType(int);
     method public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
@@ -30352,6 +30359,7 @@
     method public String getPlmn();
     method public String getRealm();
     method @Deprecated public String getSubjectMatch();
+    method public boolean isAuthenticationSimBased();
     method public void setAltSubjectMatch(String);
     method public void setAnonymousIdentity(String);
     method public void setCaCertificate(@Nullable java.security.cert.X509Certificate);
diff --git a/api/system-current.txt b/api/system-current.txt
index 42ba962..33e3f5b 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -413,6 +413,16 @@
     field public static final int UID_STATE_TOP = 200; // 0xc8
   }
 
+  public static final class AppOpsManager.HistoricalFeatureOps implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public String getFeatureId();
+    method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String);
+    method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int);
+    method @IntRange(from=0) public int getOpCount();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalFeatureOps> CREATOR;
+  }
+
   public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable {
     method public int describeContents();
     method public long getAccessCount(int, int, int);
@@ -446,6 +456,7 @@
   public static final class AppOpsManager.HistoricalOpsRequest.Builder {
     ctor public AppOpsManager.HistoricalOpsRequest.Builder(long, long);
     method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest build();
+    method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFeatureId(@Nullable String);
     method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFlags(int);
     method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setOpNames(@Nullable java.util.List<java.lang.String>);
     method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setPackageName(@Nullable String);
@@ -454,6 +465,9 @@
 
   public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable {
     method public int describeContents();
+    method @IntRange(from=0) public int getFeatureCount();
+    method @Nullable public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOps(@NonNull String);
+    method @NonNull public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOpsAt(@IntRange(from=0) int);
     method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String);
     method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int);
     method @IntRange(from=0) public int getOpCount();
@@ -785,6 +799,7 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioned();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isDeviceProvisioningConfigApplied();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedKiosk();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isOrganizationOwnedDeviceWithManagedProfile();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isUnattendedManagedKiosk();
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long);
     method @RequiresPermission("android.permission.NOTIFY_PENDING_SYSTEM_UPDATE") public void notifyPendingSystemUpdate(long, boolean);
@@ -1518,6 +1533,14 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
   }
 
+  public final class BluetoothHidHost implements android.bluetooth.BluetoothProfile {
+    method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
+    method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
+    field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
+  }
+
   public final class BluetoothPan implements android.bluetooth.BluetoothProfile {
     method protected void finalize();
     method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
@@ -1535,6 +1558,7 @@
 
   public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
     method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
     field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
   }
 
@@ -1660,6 +1684,7 @@
     field public static final String EUICC_CARD_SERVICE = "euicc_card";
     field public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
     field public static final String NETD_SERVICE = "netd";
+    field public static final String NETWORK_POLICY_SERVICE = "netpolicy";
     field public static final String NETWORK_SCORE_SERVICE = "network_score";
     field public static final String OEM_LOCK_SERVICE = "oem_lock";
     field public static final String PERMISSION_SERVICE = "permission";
@@ -2117,6 +2142,7 @@
     field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
     field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
+    field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
     field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
     field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
     field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20
@@ -4560,6 +4586,36 @@
   public class Tuner.Descrambler {
   }
 
+  public class Tuner.Filter {
+  }
+
+  public static interface Tuner.FilterCallback {
+    method public void onFilterEvent(@NonNull android.media.tv.tuner.Tuner.Filter, @NonNull android.media.tv.tuner.filter.FilterEvent[]);
+    method public void onFilterStatusChanged(@NonNull android.media.tv.tuner.Tuner.Filter, int);
+  }
+
+  public final class TunerConstants {
+    field public static final int FILTER_STATUS_DATA_READY = 1; // 0x1
+    field public static final int FILTER_STATUS_HIGH_WATER = 4; // 0x4
+    field public static final int FILTER_STATUS_LOW_WATER = 2; // 0x2
+    field public static final int FILTER_STATUS_OVERFLOW = 8; // 0x8
+  }
+
+}
+
+package android.media.tv.tuner.filter {
+
+  public abstract class FilterEvent {
+    ctor public FilterEvent();
+  }
+
+  public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent {
+    method public int getDataLength();
+    method public int getSectionNumber();
+    method public int getTableId();
+    method public int getVersion();
+  }
+
 }
 
 package android.metrics {
@@ -4768,6 +4824,7 @@
   public final class MatchAllNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
     ctor public MatchAllNetworkSpecifier();
     method public int describeContents();
+    method public boolean satisfiedBy(android.net.NetworkSpecifier);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.MatchAllNetworkSpecifier> CREATOR;
   }
@@ -4831,6 +4888,12 @@
     method public void updateScores(@NonNull java.util.List<android.net.ScoredNetwork>);
   }
 
+  public abstract class NetworkSpecifier {
+    method public void assertValidFromUid(int);
+    method @Nullable public android.net.NetworkSpecifier redact();
+    method public abstract boolean satisfiedBy(@Nullable android.net.NetworkSpecifier);
+  }
+
   public class NetworkStack {
     field public static final String PERMISSION_MAINLINE_NETWORK_STACK = "android.permission.MAINLINE_NETWORK_STACK";
   }
@@ -4901,6 +4964,7 @@
   public final class StringNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
     ctor public StringNetworkSpecifier(@NonNull String);
     method public int describeContents();
+    method public boolean satisfiedBy(android.net.NetworkSpecifier);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.StringNetworkSpecifier> CREATOR;
     field @NonNull public final String specifier;
@@ -5684,6 +5748,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApCapability> CREATOR;
     field public static final int SOFTAP_FEATURE_ACS_OFFLOAD = 1; // 0x1
     field public static final int SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT = 2; // 0x2
+    field public static final int SOFTAP_FEATURE_WPA3_SAE = 4; // 0x4
   }
 
   public final class SoftApConfiguration implements android.os.Parcelable {
@@ -5692,6 +5757,7 @@
     method @Nullable public android.net.MacAddress getBssid();
     method public int getChannel();
     method public int getMaxNumberOfClients();
+    method @Nullable public String getPassphrase();
     method public int getSecurityType();
     method @Nullable public String getSsid();
     method @Nullable public String getWpa2Passphrase();
@@ -5704,6 +5770,8 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApConfiguration> CREATOR;
     field public static final int SECURITY_TYPE_OPEN = 0; // 0x0
     field public static final int SECURITY_TYPE_WPA2_PSK = 1; // 0x1
+    field public static final int SECURITY_TYPE_WPA3_SAE = 3; // 0x3
+    field public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2; // 0x2
   }
 
   public static final class SoftApConfiguration.Builder {
@@ -5715,6 +5783,7 @@
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int);
+    method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setSsid(@Nullable String);
     method @NonNull public android.net.wifi.SoftApConfiguration.Builder setWpa2Passphrase(@Nullable String);
   }
@@ -5935,6 +6004,7 @@
     field public static final String EXTRA_OSU_NETWORK = "android.net.wifi.extra.OSU_NETWORK";
     field public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
     field public static final String EXTRA_URL = "android.net.wifi.extra.URL";
+    field public static final String EXTRA_WIFI_AP_FAILURE_REASON = "android.net.wifi.extra.WIFI_AP_FAILURE_REASON";
     field public static final String EXTRA_WIFI_AP_INTERFACE_NAME = "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME";
     field public static final String EXTRA_WIFI_AP_MODE = "android.net.wifi.extra.WIFI_AP_MODE";
     field public static final String EXTRA_WIFI_AP_STATE = "wifi_state";
@@ -6013,6 +6083,10 @@
     field public int numUsage;
   }
 
+  public final class WifiNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+    method public boolean satisfiedBy(android.net.NetworkSpecifier);
+  }
+
   public static final class WifiNetworkSuggestion.Builder {
     method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
   }
@@ -6199,6 +6273,10 @@
     method @Deprecated public android.net.NetworkSpecifier createNetworkSpecifierPmk(@NonNull android.net.wifi.aware.PeerHandle, @NonNull byte[]);
   }
 
+  public final class WifiAwareNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+    method public boolean satisfiedBy(android.net.NetworkSpecifier);
+  }
+
   public class WifiAwareSession implements java.lang.AutoCloseable {
     method public android.net.NetworkSpecifier createNetworkSpecifierPmk(int, @NonNull byte[], @NonNull byte[]);
   }
@@ -6913,9 +6991,12 @@
 
   public class RecoverySystem {
     method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void cancelScheduledUpdate(android.content.Context) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.RECOVERY) public static boolean clearPrepareForUnattendedUpdate(@NonNull android.content.Context) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void installPackage(android.content.Context, java.io.File, boolean) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void prepareForUnattendedUpdate(@NonNull android.content.Context, @NonNull String, @Nullable android.content.IntentSender) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener, android.os.Handler) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener) throws java.io.IOException;
+    method @RequiresPermission(android.Manifest.permission.RECOVERY) public static boolean rebootAndApply(@NonNull android.content.Context, @NonNull String, @NonNull String) throws java.io.IOException;
     method @RequiresPermission(allOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static void rebootWipeAb(android.content.Context, java.io.File, String) throws java.io.IOException;
     method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void scheduleUpdateOnBoot(android.content.Context, java.io.File) throws java.io.IOException;
     method public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException;
@@ -7002,11 +7083,13 @@
 
   public class UpdateEngine {
     ctor public UpdateEngine();
+    method @NonNull public android.os.UpdateEngine.AllocateSpaceResult allocateSpace(@NonNull String, @NonNull String[]);
     method public void applyPayload(String, long, long, String[]);
     method public void applyPayload(@NonNull android.os.ParcelFileDescriptor, long, long, @NonNull String[]);
     method public boolean bind(android.os.UpdateEngineCallback, android.os.Handler);
     method public boolean bind(android.os.UpdateEngineCallback);
     method public void cancel();
+    method public int cleanupAppliedPayload();
     method public void resetStatus();
     method public void resume();
     method public void suspend();
@@ -7014,14 +7097,21 @@
     method public boolean verifyPayloadMetadata(String);
   }
 
+  public static final class UpdateEngine.AllocateSpaceResult {
+    method public int errorCode();
+    method public long freeSpaceRequired();
+  }
+
   public static final class UpdateEngine.ErrorCodeConstants {
     ctor public UpdateEngine.ErrorCodeConstants();
+    field public static final int DEVICE_CORRUPTED = 61; // 0x3d
     field public static final int DOWNLOAD_PAYLOAD_VERIFICATION_ERROR = 12; // 0xc
     field public static final int DOWNLOAD_TRANSFER_ERROR = 9; // 0x9
     field public static final int ERROR = 1; // 0x1
     field public static final int FILESYSTEM_COPIER_ERROR = 4; // 0x4
     field public static final int INSTALL_DEVICE_OPEN_ERROR = 7; // 0x7
     field public static final int KERNEL_DEVICE_OPEN_ERROR = 8; // 0x8
+    field public static final int NOT_ENOUGH_SPACE = 60; // 0x3c
     field public static final int PAYLOAD_HASH_MISMATCH_ERROR = 10; // 0xa
     field public static final int PAYLOAD_MISMATCHED_TYPE_ERROR = 6; // 0x6
     field public static final int PAYLOAD_SIZE_MISMATCH_ERROR = 11; // 0xb
@@ -9101,30 +9191,37 @@
 
   public abstract class CellIdentity implements android.os.Parcelable {
     method @NonNull public abstract android.telephony.CellLocation asCellLocation();
+    method @NonNull public abstract android.telephony.CellIdentity sanitizeLocationInfo();
   }
 
   public final class CellIdentityCdma extends android.telephony.CellIdentity {
     method @NonNull public android.telephony.cdma.CdmaCellLocation asCellLocation();
+    method @NonNull public android.telephony.CellIdentityCdma sanitizeLocationInfo();
   }
 
   public final class CellIdentityGsm extends android.telephony.CellIdentity {
     method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+    method @NonNull public android.telephony.CellIdentityGsm sanitizeLocationInfo();
   }
 
   public final class CellIdentityLte extends android.telephony.CellIdentity {
     method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+    method @NonNull public android.telephony.CellIdentityLte sanitizeLocationInfo();
   }
 
   public final class CellIdentityNr extends android.telephony.CellIdentity {
     method @NonNull public android.telephony.CellLocation asCellLocation();
+    method @NonNull public android.telephony.CellIdentityNr sanitizeLocationInfo();
   }
 
   public final class CellIdentityTdscdma extends android.telephony.CellIdentity {
     method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+    method @NonNull public android.telephony.CellIdentityTdscdma sanitizeLocationInfo();
   }
 
   public final class CellIdentityWcdma extends android.telephony.CellIdentity {
     method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+    method @NonNull public android.telephony.CellIdentityWcdma sanitizeLocationInfo();
   }
 
   public final class DataFailCause {
@@ -9524,6 +9621,7 @@
     field public static final int IMS_ACCESS_BLOCKED = 60; // 0x3c
     field public static final int IMS_MERGED_SUCCESSFULLY = 45; // 0x2d
     field public static final int IMS_SIP_ALTERNATE_EMERGENCY_CALL = 71; // 0x47
+    field public static final int INCOMING_AUTO_REJECTED = 81; // 0x51
     field public static final int INCOMING_MISSED = 1; // 0x1
     field public static final int INCOMING_REJECTED = 16; // 0x10
     field public static final int INVALID_CREDENTIALS = 10; // 0xa
@@ -10016,6 +10114,7 @@
     method public boolean canManageSubscription(@Nullable android.telephony.SubscriptionInfo, @Nullable String);
     method @NonNull public int[] getActiveAndHiddenSubscriptionIdList();
     method @NonNull public int[] getActiveSubscriptionIdList();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfoForIcc(@NonNull String);
     method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
     method @NonNull public static android.content.res.Resources getResourcesForSubId(@NonNull android.content.Context, int);
@@ -10680,6 +10779,7 @@
     field public static final int DIALSTRING_USSD = 2; // 0x2
     field public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
     field public static final String EXTRA_ADDITIONAL_SIP_INVITE_FIELDS = "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS";
+    field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE";
     field public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
     field public static final String EXTRA_CHILD_NUMBER = "ChildNum";
     field public static final String EXTRA_CNA = "cna";
diff --git a/api/test-current.txt b/api/test-current.txt
index cb706dc..9967942 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -244,6 +244,16 @@
     field public static final int UID_STATE_TOP = 200; // 0xc8
   }
 
+  public static final class AppOpsManager.HistoricalFeatureOps implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public String getFeatureId();
+    method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String);
+    method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int);
+    method @IntRange(from=0) public int getOpCount();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalFeatureOps> CREATOR;
+  }
+
   public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable {
     method public int describeContents();
     method public long getAccessCount(int, int, int);
@@ -268,9 +278,9 @@
     method @IntRange(from=0) public int getUidCount();
     method @Nullable public android.app.AppOpsManager.HistoricalUidOps getUidOps(int);
     method @NonNull public android.app.AppOpsManager.HistoricalUidOps getUidOpsAt(@IntRange(from=0) int);
-    method public void increaseAccessCount(int, int, @NonNull String, int, int, long);
-    method public void increaseAccessDuration(int, int, @NonNull String, int, int, long);
-    method public void increaseRejectCount(int, int, @NonNull String, int, int, long);
+    method public void increaseAccessCount(int, int, @NonNull String, @Nullable String, int, int, long);
+    method public void increaseAccessDuration(int, int, @NonNull String, @Nullable String, int, int, long);
+    method public void increaseRejectCount(int, int, @NonNull String, @Nullable String, int, int, long);
     method public void offsetBeginAndEndTime(long);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOps> CREATOR;
@@ -282,6 +292,7 @@
   public static final class AppOpsManager.HistoricalOpsRequest.Builder {
     ctor public AppOpsManager.HistoricalOpsRequest.Builder(long, long);
     method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest build();
+    method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFeatureId(@Nullable String);
     method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFlags(int);
     method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setOpNames(@Nullable java.util.List<java.lang.String>);
     method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setPackageName(@Nullable String);
@@ -290,6 +301,9 @@
 
   public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable {
     method public int describeContents();
+    method @IntRange(from=0) public int getFeatureCount();
+    method @Nullable public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOps(@NonNull String);
+    method @NonNull public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOpsAt(@IntRange(from=0) int);
     method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String);
     method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int);
     method @IntRange(from=0) public int getOpCount();
@@ -3380,6 +3394,7 @@
     field public static final int DIALSTRING_USSD = 2; // 0x2
     field public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
     field public static final String EXTRA_ADDITIONAL_SIP_INVITE_FIELDS = "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS";
+    field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE";
     field public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
     field public static final String EXTRA_CHILD_NUMBER = "ChildNum";
     field public static final String EXTRA_CNA = "cna";
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 5ff0f97..b2a5b50 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -1135,12 +1135,11 @@
     }
 }
 
-Status StatsService::getData(int64_t key, const String16& packageName, vector<uint8_t>* output) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+Status StatsService::getData(int64_t key, const int32_t callingUid, vector<uint8_t>* output) {
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    VLOG("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid());
-    ConfigKey configKey(ipc->getCallingUid(), key);
+    VLOG("StatsService::getData with Uid %i", callingUid);
+    ConfigKey configKey(callingUid, key);
     // The dump latency does not matter here since we do not include the current bucket, we do not
     // need to pull any new data anyhow.
     mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), false /* include_current_bucket*/,
@@ -1148,12 +1147,9 @@
     return Status::ok();
 }
 
-Status StatsService::getMetadata(const String16& packageName, vector<uint8_t>* output) {
-    ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+Status StatsService::getMetadata(vector<uint8_t>* output) {
+    ENFORCE_UID(AID_SYSTEM);
 
-    IPCThreadState* ipc = IPCThreadState::self();
-    VLOG("StatsService::getMetadata with Pid %i, Uid %i", ipc->getCallingPid(),
-         ipc->getCallingUid());
     StatsdStats::getInstance().dumpStats(output, false); // Don't reset the counters.
     return Status::ok();
 }
@@ -1477,17 +1473,7 @@
 
 
 Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) {
-    uid_t uid = IPCThreadState::self()->getCallingUid();
-
-    // Caller must be granted these permissions
-    if (!checkCallingPermission(String16(kPermissionDump))) {
-        return exception(binder::Status::EX_SECURITY,
-                         StringPrintf("UID %d lacks permission %s", uid, kPermissionDump));
-    }
-    if (!checkCallingPermission(String16(kPermissionUsage))) {
-        return exception(binder::Status::EX_SECURITY,
-                         StringPrintf("UID %d lacks permission %s", uid, kPermissionUsage));
-    }
+    ENFORCE_UID(AID_SYSTEM);
     // TODO: add verifier permission
 
     // Read the latest train info
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 0565f3c..56d87f2 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -99,15 +99,14 @@
      * Binder call for clients to request data for this configuration key.
      */
     virtual Status getData(int64_t key,
-                           const String16& packageName,
+                           const int32_t callingUid,
                            vector<uint8_t>* output) override;
 
 
     /**
      * Binder call for clients to get metadata across all configs in statsd.
      */
-    virtual Status getMetadata(const String16& packageName,
-                               vector<uint8_t>* output) override;
+    virtual Status getMetadata(vector<uint8_t>* output) override;
 
 
     /**
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index de7cc9d..032e824 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -278,6 +278,9 @@
     public abstract boolean isBackgroundActivityStartsEnabled();
     public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing);
 
+    /** @see com.android.server.am.ActivityManagerService#monitor */
+    public abstract void monitor();
+
     /** Input dispatch timeout to a window, start the ANR process. */
     public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason);
     public abstract boolean inputDispatchingTimedOut(Object proc, String activityShortComponentName,
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 42c4d36..4a8e4e2 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -3917,10 +3917,53 @@
         void visitHistoricalOps(@NonNull HistoricalOps ops);
         void visitHistoricalUidOps(@NonNull HistoricalUidOps ops);
         void visitHistoricalPackageOps(@NonNull HistoricalPackageOps ops);
+        void visitHistoricalFeatureOps(@NonNull HistoricalFeatureOps ops);
         void visitHistoricalOp(@NonNull HistoricalOp ops);
     }
 
     /**
+     * Specifies what parameters to filter historical appop requests for
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "FILTER_BY_" }, value = {
+            FILTER_BY_UID,
+            FILTER_BY_PACKAGE_NAME,
+            FILTER_BY_FEATURE_ID,
+            FILTER_BY_OP_NAMES
+    })
+    public @interface HistoricalOpsRequestFilter {}
+
+    /**
+     * Filter historical appop request by uid.
+     *
+     * @hide
+     */
+    public static final int FILTER_BY_UID = 1<<0;
+
+    /**
+     * Filter historical appop request by package name.
+     *
+     * @hide
+     */
+    public static final int FILTER_BY_PACKAGE_NAME = 1<<1;
+
+    /**
+     * Filter historical appop request by feature id.
+     *
+     * @hide
+     */
+    public static final int FILTER_BY_FEATURE_ID = 1<<2;
+
+    /**
+     * Filter historical appop request by op names.
+     *
+     * @hide
+     */
+    public static final int FILTER_BY_OP_NAMES = 1<<3;
+
+    /**
      * Request for getting historical app op usage. The request acts
      * as a filtering criteria when querying historical op usage.
      *
@@ -3932,17 +3975,22 @@
     public static final class HistoricalOpsRequest {
         private final int mUid;
         private final @Nullable String mPackageName;
+        private final @Nullable String mFeatureId;
         private final @Nullable List<String> mOpNames;
+        private final @HistoricalOpsRequestFilter int mFilter;
         private final long mBeginTimeMillis;
         private final long mEndTimeMillis;
         private final @OpFlags int mFlags;
 
         private HistoricalOpsRequest(int uid, @Nullable String packageName,
-                @Nullable List<String> opNames, long beginTimeMillis, long endTimeMillis,
-                @OpFlags int flags) {
+                @Nullable String featureId, @Nullable List<String> opNames,
+                @HistoricalOpsRequestFilter int filter, long beginTimeMillis,
+                long endTimeMillis, @OpFlags int flags) {
             mUid = uid;
             mPackageName = packageName;
+            mFeatureId = featureId;
             mOpNames = opNames;
+            mFilter = filter;
             mBeginTimeMillis = beginTimeMillis;
             mEndTimeMillis = endTimeMillis;
             mFlags = flags;
@@ -3958,7 +4006,9 @@
         public static final class Builder {
             private int mUid = Process.INVALID_UID;
             private @Nullable String mPackageName;
+            private @Nullable String mFeatureId;
             private @Nullable List<String> mOpNames;
+            private @HistoricalOpsRequestFilter int mFilter;
             private final long mBeginTimeMillis;
             private final long mEndTimeMillis;
             private @OpFlags int mFlags = OP_FLAGS_ALL;
@@ -3991,6 +4041,13 @@
                 Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0,
                         "uid must be " + Process.INVALID_UID + " or non negative");
                 mUid = uid;
+
+                if (uid == Process.INVALID_UID) {
+                    mFilter &= ~FILTER_BY_UID;
+                } else {
+                    mFilter |= FILTER_BY_UID;
+                }
+
                 return this;
             }
 
@@ -4002,6 +4059,26 @@
              */
             public @NonNull Builder setPackageName(@Nullable String packageName) {
                 mPackageName = packageName;
+
+                if (packageName == null) {
+                    mFilter &= ~FILTER_BY_PACKAGE_NAME;
+                } else {
+                    mFilter |= FILTER_BY_PACKAGE_NAME;
+                }
+
+                return this;
+            }
+
+            /**
+             * Sets the feature id to query for.
+             *
+             * @param featureId The id of the feature.
+             * @return This builder.
+             */
+            public @NonNull Builder setFeatureId(@Nullable String featureId) {
+                mFeatureId = featureId;
+                mFilter |= FILTER_BY_FEATURE_ID;
+
                 return this;
             }
 
@@ -4020,6 +4097,13 @@
                     }
                 }
                 mOpNames = opNames;
+
+                if (mOpNames == null) {
+                    mFilter &= ~FILTER_BY_OP_NAMES;
+                } else {
+                    mFilter |= FILTER_BY_OP_NAMES;
+                }
+
                 return this;
             }
 
@@ -4044,8 +4128,8 @@
              * @return a new {@link HistoricalOpsRequest}.
              */
             public @NonNull HistoricalOpsRequest build() {
-                return new HistoricalOpsRequest(mUid, mPackageName, mOpNames,
-                        mBeginTimeMillis, mEndTimeMillis, mFlags);
+                return new HistoricalOpsRequest(mUid, mPackageName, mFeatureId, mOpNames,
+                        mFilter, mBeginTimeMillis, mEndTimeMillis, mFlags);
             }
         }
     }
@@ -4202,15 +4286,18 @@
         /**
          * AppPermissionUsage the ops to leave only the data we filter for.
          *
-         * @param uid Uid to filter for or {@link android.os.Process#INCIDENTD_UID} for all.
-         * @param packageName Package to filter for or null for all.
-         * @param opNames Ops to filter for or null for all.
+         * @param uid Uid to filter for.
+         * @param packageName Package to filter for.
+         * @param featureId Package to filter for.
+         * @param opNames Ops to filter for.
+         * @param filter Which parameters to filter on.
          * @param beginTimeMillis The begin time to filter for or {@link Long#MIN_VALUE} for all.
          * @param endTimeMillis The end time to filter for or {@link Long#MAX_VALUE} for all.
          *
          * @hide
          */
-        public void filter(int uid, @Nullable String packageName, @Nullable String[] opNames,
+        public void filter(int uid, @Nullable String packageName, @Nullable String featureId,
+                @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
                 long beginTimeMillis, long endTimeMillis) {
             final long durationMillis = getDurationMillis();
             mBeginTimeMillis = Math.max(mBeginTimeMillis, beginTimeMillis);
@@ -4220,10 +4307,10 @@
             final int uidCount = getUidCount();
             for (int i = uidCount - 1; i >= 0; i--) {
                 final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i);
-                if (uid != Process.INVALID_UID && uid != uidOp.getUid()) {
+                if ((filter & FILTER_BY_UID) != 0 && uid != uidOp.getUid()) {
                     mHistoricalUidOps.removeAt(i);
                 } else {
-                    uidOp.filter(packageName, opNames, scaleFactor);
+                    uidOp.filter(packageName, featureId, opNames, filter, scaleFactor);
                 }
             }
         }
@@ -4251,25 +4338,28 @@
         /** @hide */
         @TestApi
         public void increaseAccessCount(int opCode, int uid, @NonNull String packageName,
-                @UidState int uidState,  @OpFlags int flags, long increment) {
+                @Nullable String featureId, @UidState int uidState,  @OpFlags int flags,
+                long increment) {
             getOrCreateHistoricalUidOps(uid).increaseAccessCount(opCode,
-                    packageName, uidState, flags, increment);
+                    packageName, featureId, uidState, flags, increment);
         }
 
         /** @hide */
         @TestApi
         public void increaseRejectCount(int opCode, int uid, @NonNull String packageName,
-                @UidState int uidState, @OpFlags int flags, long increment) {
+                @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+                long increment) {
             getOrCreateHistoricalUidOps(uid).increaseRejectCount(opCode,
-                    packageName, uidState, flags, increment);
+                    packageName, featureId, uidState, flags, increment);
         }
 
         /** @hide */
         @TestApi
         public void increaseAccessDuration(int opCode, int uid, @NonNull String packageName,
-                @UidState int uidState, @OpFlags int flags, long increment) {
+                @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+                long increment) {
             getOrCreateHistoricalUidOps(uid).increaseAccessDuration(opCode,
-                    packageName, uidState, flags, increment);
+                    packageName, featureId, uidState, flags, increment);
         }
 
         /** @hide */
@@ -4549,15 +4639,17 @@
             }
         }
 
-        private void filter(@Nullable String packageName, @Nullable String[] opNames,
+        private void filter(@Nullable String packageName, @Nullable String featureId,
+                @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
                 double fractionToRemove) {
             final int packageCount = getPackageCount();
             for (int i = packageCount - 1; i >= 0; i--) {
                 final HistoricalPackageOps packageOps = getPackageOpsAt(i);
-                if (packageName != null && !packageName.equals(packageOps.getPackageName())) {
+                if ((filter & FILTER_BY_PACKAGE_NAME) != 0 && !packageName.equals(
+                        packageOps.getPackageName())) {
                     mHistoricalPackageOps.removeAt(i);
                 } else {
-                    packageOps.filter(opNames, fractionToRemove);
+                    packageOps.filter(featureId, opNames, filter, fractionToRemove);
                 }
             }
         }
@@ -4574,21 +4666,24 @@
         }
 
         private void increaseAccessCount(int opCode, @NonNull String packageName,
-                @UidState int uidState, @OpFlags int flags, long increment) {
+                @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+                long increment) {
             getOrCreateHistoricalPackageOps(packageName).increaseAccessCount(
-                    opCode, uidState, flags, increment);
+                    opCode, featureId, uidState, flags, increment);
         }
 
         private void increaseRejectCount(int opCode, @NonNull String packageName,
-                @UidState int uidState,  @OpFlags int flags, long increment) {
+                @Nullable String featureId, @UidState int uidState,  @OpFlags int flags,
+                long increment) {
             getOrCreateHistoricalPackageOps(packageName).increaseRejectCount(
-                    opCode, uidState, flags, increment);
+                    opCode, featureId, uidState, flags, increment);
         }
 
         private void increaseAccessDuration(int opCode, @NonNull String packageName,
-                @UidState int uidState, @OpFlags int flags, long increment) {
+                @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+                long increment) {
             getOrCreateHistoricalPackageOps(packageName).increaseAccessDuration(
-                    opCode, uidState, flags, increment);
+                    opCode, featureId, uidState, flags, increment);
         }
 
         /**
@@ -4733,7 +4828,7 @@
     @SystemApi
     public static final class HistoricalPackageOps implements Parcelable {
         private final @NonNull String mPackageName;
-        private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps;
+        private @Nullable ArrayMap<String, HistoricalFeatureOps> mHistoricalFeatureOps;
 
         /** @hide */
         public HistoricalPackageOps(@NonNull String packageName) {
@@ -4742,6 +4837,339 @@
 
         private HistoricalPackageOps(@NonNull HistoricalPackageOps other) {
             mPackageName = other.mPackageName;
+            final int opCount = other.getFeatureCount();
+            for (int i = 0; i < opCount; i++) {
+                final HistoricalFeatureOps origOps = other.getFeatureOpsAt(i);
+                final HistoricalFeatureOps cloneOps = new HistoricalFeatureOps(origOps);
+                if (mHistoricalFeatureOps == null) {
+                    mHistoricalFeatureOps = new ArrayMap<>(opCount);
+                }
+                mHistoricalFeatureOps.put(cloneOps.getFeatureId(), cloneOps);
+            }
+        }
+
+        private HistoricalPackageOps(@NonNull Parcel parcel) {
+            mPackageName = parcel.readString();
+            mHistoricalFeatureOps = parcel.createTypedArrayMap(HistoricalFeatureOps.CREATOR);
+        }
+
+        private @Nullable HistoricalPackageOps splice(double fractionToRemove) {
+            HistoricalPackageOps splice = null;
+            final int featureCount = getFeatureCount();
+            for (int i = 0; i < featureCount; i++) {
+                final HistoricalFeatureOps origOps = getFeatureOpsAt(i);
+                final HistoricalFeatureOps spliceOps = origOps.splice(fractionToRemove);
+                if (spliceOps != null) {
+                    if (splice == null) {
+                        splice = new HistoricalPackageOps(mPackageName);
+                    }
+                    if (splice.mHistoricalFeatureOps == null) {
+                        splice.mHistoricalFeatureOps = new ArrayMap<>();
+                    }
+                    splice.mHistoricalFeatureOps.put(spliceOps.getFeatureId(), spliceOps);
+                }
+            }
+            return splice;
+        }
+
+        private void merge(@NonNull HistoricalPackageOps other) {
+            final int featureCount = other.getFeatureCount();
+            for (int i = 0; i < featureCount; i++) {
+                final HistoricalFeatureOps otherFeatureOps = other.getFeatureOpsAt(i);
+                final HistoricalFeatureOps thisFeatureOps = getFeatureOps(
+                        otherFeatureOps.getFeatureId());
+                if (thisFeatureOps != null) {
+                    thisFeatureOps.merge(otherFeatureOps);
+                } else {
+                    if (mHistoricalFeatureOps == null) {
+                        mHistoricalFeatureOps = new ArrayMap<>();
+                    }
+                    mHistoricalFeatureOps.put(otherFeatureOps.getFeatureId(), otherFeatureOps);
+                }
+            }
+        }
+
+        private void filter(@Nullable String featureId, @Nullable String[] opNames,
+                @HistoricalOpsRequestFilter int filter, double fractionToRemove) {
+            final int featureCount = getFeatureCount();
+            for (int i = featureCount - 1; i >= 0; i--) {
+                final HistoricalFeatureOps featureOps = getFeatureOpsAt(i);
+                if ((filter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(featureId,
+                        featureOps.getFeatureId())) {
+                    mHistoricalFeatureOps.removeAt(i);
+                } else {
+                    featureOps.filter(opNames, filter, fractionToRemove);
+                }
+            }
+        }
+
+        private void accept(@NonNull HistoricalOpsVisitor visitor) {
+            visitor.visitHistoricalPackageOps(this);
+            final int featureCount = getFeatureCount();
+            for (int i = 0; i < featureCount; i++) {
+                getFeatureOpsAt(i).accept(visitor);
+            }
+        }
+
+        private boolean isEmpty() {
+            final int featureCount = getFeatureCount();
+            for (int i = featureCount - 1; i >= 0; i--) {
+                final HistoricalFeatureOps featureOps = mHistoricalFeatureOps.valueAt(i);
+                if (!featureOps.isEmpty()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private void increaseAccessCount(int opCode, @Nullable String featureId,
+                @UidState int uidState, @OpFlags int flags, long increment) {
+            getOrCreateHistoricalFeatureOps(featureId).increaseAccessCount(
+                    opCode, uidState, flags, increment);
+        }
+
+        private void increaseRejectCount(int opCode, @Nullable String featureId,
+                @UidState int uidState, @OpFlags int flags, long increment) {
+            getOrCreateHistoricalFeatureOps(featureId).increaseRejectCount(
+                    opCode, uidState, flags, increment);
+        }
+
+        private void increaseAccessDuration(int opCode, @Nullable String featureId,
+                @UidState int uidState, @OpFlags int flags, long increment) {
+            getOrCreateHistoricalFeatureOps(featureId).increaseAccessDuration(
+                    opCode, uidState, flags, increment);
+        }
+
+        /**
+         * Gets the package name which the data represents.
+         *
+         * @return The package name which the data represents.
+         */
+        public @NonNull String getPackageName() {
+            return mPackageName;
+        }
+
+        private @NonNull HistoricalFeatureOps getOrCreateHistoricalFeatureOps(
+                @Nullable String featureId) {
+            if (mHistoricalFeatureOps == null) {
+                mHistoricalFeatureOps = new ArrayMap<>();
+            }
+            HistoricalFeatureOps historicalFeatureOp = mHistoricalFeatureOps.get(featureId);
+            if (historicalFeatureOp == null) {
+                historicalFeatureOp = new HistoricalFeatureOps(featureId);
+                mHistoricalFeatureOps.put(featureId, historicalFeatureOp);
+            }
+            return historicalFeatureOp;
+        }
+
+        /**
+         * Gets number historical app ops.
+         *
+         * @return The number historical app ops.
+         * @see #getOpAt(int)
+         */
+        public @IntRange(from = 0) int getOpCount() {
+            int numOps = 0;
+            int numFeatures = getFeatureCount();
+
+            for (int code = 0; code < _NUM_OP; code++) {
+                String opName = opToPublicName(code);
+
+                for (int featureNum = 0; featureNum < numFeatures; featureNum++) {
+                    if (getFeatureOpsAt(featureNum).getOp(opName) != null) {
+                        numOps++;
+                        break;
+                    }
+                }
+            }
+
+            return numOps;
+        }
+
+        /**
+         * Gets the historical op at a given index.
+         *
+         * <p>This combines the counts from all features.
+         *
+         * @param index The index to lookup.
+         * @return The op at the given index.
+         * @see #getOpCount()
+         */
+        public @NonNull HistoricalOp getOpAt(@IntRange(from = 0) int index) {
+            int numOpsFound = 0;
+            int numFeatures = getFeatureCount();
+
+            for (int code = 0; code < _NUM_OP; code++) {
+                String opName = opToPublicName(code);
+
+                for (int featureNum = 0; featureNum < numFeatures; featureNum++) {
+                    if (getFeatureOpsAt(featureNum).getOp(opName) != null) {
+                        if (numOpsFound == index) {
+                            return getOp(opName);
+                        } else {
+                            numOpsFound++;
+                            break;
+                        }
+                    }
+                }
+            }
+
+            throw new IndexOutOfBoundsException();
+        }
+
+        /**
+         * Gets the historical entry for a given op name.
+         *
+         * <p>This combines the counts from all features.
+         *
+         * @param opName The op name.
+         * @return The historical entry for that op name.
+         */
+        public @Nullable HistoricalOp getOp(@NonNull String opName) {
+            if (mHistoricalFeatureOps == null) {
+                return null;
+            }
+
+            HistoricalOp combinedOp = null;
+            int numFeatures = getFeatureCount();
+            for (int i = 0; i < numFeatures; i++) {
+                HistoricalOp featureOp = getFeatureOpsAt(i).getOp(opName);
+                if (featureOp != null) {
+                    if (combinedOp == null) {
+                        combinedOp = new HistoricalOp(featureOp);
+                    } else {
+                        combinedOp.merge(featureOp);
+                    }
+                }
+            }
+
+            return combinedOp;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel parcel, int flags) {
+            parcel.writeString(mPackageName);
+            parcel.writeTypedArrayMap(mHistoricalFeatureOps, flags);
+        }
+
+        public static final @android.annotation.NonNull Creator<HistoricalPackageOps> CREATOR =
+                new Creator<HistoricalPackageOps>() {
+            @Override
+            public @NonNull HistoricalPackageOps createFromParcel(@NonNull Parcel parcel) {
+                return new HistoricalPackageOps(parcel);
+            }
+
+            @Override
+            public @NonNull HistoricalPackageOps[] newArray(int size) {
+                return new HistoricalPackageOps[size];
+            }
+        };
+
+        @Override
+        public boolean equals(@Nullable Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || getClass() != obj.getClass()) {
+                return false;
+            }
+            final HistoricalPackageOps other = (HistoricalPackageOps) obj;
+            if (!mPackageName.equals(other.mPackageName)) {
+                return false;
+            }
+            if (mHistoricalFeatureOps == null) {
+                if (other.mHistoricalFeatureOps != null) {
+                    return false;
+                }
+            } else if (!mHistoricalFeatureOps.equals(other.mHistoricalFeatureOps)) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = mPackageName != null ? mPackageName.hashCode() : 0;
+            result = 31 * result + (mHistoricalFeatureOps != null ? mHistoricalFeatureOps.hashCode()
+                    : 0);
+            return result;
+        }
+
+        /**
+         * Gets number of feature with historical ops.
+         *
+         * @return The number of feature with historical ops.
+         *
+         * @see #getFeatureOpsAt(int)
+         */
+        public @IntRange(from = 0) int getFeatureCount() {
+            if (mHistoricalFeatureOps == null) {
+                return 0;
+            }
+            return mHistoricalFeatureOps.size();
+        }
+
+        /**
+         * Gets the historical feature ops at a given index.
+         *
+         * @param index The index.
+         *
+         * @return The historical feature ops at the given index.
+         *
+         * @see #getFeatureCount()
+         */
+        public @NonNull HistoricalFeatureOps getFeatureOpsAt(@IntRange(from = 0) int index) {
+            if (mHistoricalFeatureOps == null) {
+                throw new IndexOutOfBoundsException();
+            }
+            return mHistoricalFeatureOps.valueAt(index);
+        }
+
+        /**
+         * Gets the historical feature ops for a given feature.
+         *
+         * @param featureId The feature id.
+         *
+         * @return The historical ops for the feature.
+         */
+        public @Nullable HistoricalFeatureOps getFeatureOps(@NonNull String featureId) {
+            if (mHistoricalFeatureOps == null) {
+                return null;
+            }
+            return mHistoricalFeatureOps.get(featureId);
+        }
+    }
+
+    /**
+     * This class represents historical app op information about a feature in a package.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    /* codegen verifier cannot deal with nested class parameters
+    @DataClass(genHiddenConstructor = true,
+            genEqualsHashCode = true, genHiddenCopyConstructor = true) */
+    @DataClass.Suppress("getHistoricalOps")
+    public static final class HistoricalFeatureOps implements Parcelable {
+        /** Id of the {@link Context#createFeatureContext feature} in the package */
+        private final @Nullable String mFeatureId;
+
+        /** Ops for this feature */
+        private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps;
+
+        /** @hide */
+        public HistoricalFeatureOps(@NonNull String featureId) {
+            mFeatureId = featureId;
+        }
+
+        private HistoricalFeatureOps(@NonNull HistoricalFeatureOps other) {
+            mFeatureId = other.mFeatureId;
             final int opCount = other.getOpCount();
             for (int i = 0; i < opCount; i++) {
                 final HistoricalOp origOp = other.getOpAt(i);
@@ -4753,20 +5181,15 @@
             }
         }
 
-        private HistoricalPackageOps(@NonNull Parcel parcel) {
-            mPackageName = parcel.readString();
-            mHistoricalOps = parcel.createTypedArrayMap(HistoricalOp.CREATOR);
-        }
-
-        private @Nullable HistoricalPackageOps splice(double fractionToRemove) {
-            HistoricalPackageOps splice = null;
+        private @Nullable HistoricalFeatureOps splice(double fractionToRemove) {
+            HistoricalFeatureOps splice = null;
             final int opCount = getOpCount();
             for (int i = 0; i < opCount; i++) {
                 final HistoricalOp origOps = getOpAt(i);
                 final HistoricalOp spliceOps = origOps.splice(fractionToRemove);
                 if (spliceOps != null) {
                     if (splice == null) {
-                        splice = new HistoricalPackageOps(mPackageName);
+                        splice = new HistoricalFeatureOps(mFeatureId, null);
                     }
                     if (splice.mHistoricalOps == null) {
                         splice.mHistoricalOps = new ArrayMap<>();
@@ -4777,7 +5200,7 @@
             return splice;
         }
 
-        private void merge(@NonNull HistoricalPackageOps other) {
+        private void merge(@NonNull HistoricalFeatureOps other) {
             final int opCount = other.getOpCount();
             for (int i = 0; i < opCount; i++) {
                 final HistoricalOp otherOp = other.getOpAt(i);
@@ -4793,11 +5216,13 @@
             }
         }
 
-        private void filter(@Nullable String[] opNames, double scaleFactor) {
+        private void filter(@Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
+                double scaleFactor) {
             final int opCount = getOpCount();
             for (int i = opCount - 1; i >= 0; i--) {
                 final HistoricalOp op = mHistoricalOps.valueAt(i);
-                if (opNames != null && !ArrayUtils.contains(opNames, op.getOpName())) {
+                if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNames,
+                        op.getOpName())) {
                     mHistoricalOps.removeAt(i);
                 } else {
                     op.filter(scaleFactor);
@@ -4832,15 +5257,6 @@
         }
 
         /**
-         * Gets the package name which the data represents.
-         *
-         * @return The package name which the data represents.
-         */
-        public @NonNull String getPackageName() {
-            return mPackageName;
-        }
-
-        /**
          * Gets number historical app ops.
          *
          * @return The number historical app ops.
@@ -4880,19 +5296,8 @@
             return mHistoricalOps.get(opName);
         }
 
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(@NonNull Parcel parcel, int flags) {
-            parcel.writeString(mPackageName);
-            parcel.writeTypedArrayMap(mHistoricalOps, flags);
-        }
-
         private void accept(@NonNull HistoricalOpsVisitor visitor) {
-            visitor.visitHistoricalPackageOps(this);
+            visitor.visitHistoricalFeatureOps(this);
             final int opCount = getOpCount();
             for (int i = 0; i < opCount; i++) {
                 getOpAt(i).accept(visitor);
@@ -4912,47 +5317,143 @@
             return op;
         }
 
-        public static final @android.annotation.NonNull Creator<HistoricalPackageOps> CREATOR =
-                new Creator<HistoricalPackageOps>() {
+
+
+        // Code below generated by codegen v1.0.14.
+        //
+        // DO NOT MODIFY!
+        // CHECKSTYLE:OFF Generated code
+        //
+        // To regenerate run:
+        // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/AppOpsManager.java
+        //
+        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+        //   Settings > Editor > Code Style > Formatter Control
+        //@formatter:off
+
+
+        /**
+         * Creates a new HistoricalFeatureOps.
+         *
+         * @param featureId
+         *   Id of the {@link Context#createFeatureContext feature} in the package
+         * @param historicalOps
+         *   Ops for this feature
+         * @hide
+         */
+        @DataClass.Generated.Member
+        public HistoricalFeatureOps(
+                @Nullable String featureId,
+                @Nullable ArrayMap<String,HistoricalOp> historicalOps) {
+            this.mFeatureId = featureId;
+            this.mHistoricalOps = historicalOps;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        /**
+         * Id of the {@link Context#createFeatureContext feature} in the package
+         */
+        @DataClass.Generated.Member
+        public @Nullable String getFeatureId() {
+            return mFeatureId;
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public boolean equals(@Nullable Object o) {
+            // You can override field equality logic by defining either of the methods like:
+            // boolean fieldNameEquals(HistoricalFeatureOps other) { ... }
+            // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            @SuppressWarnings("unchecked")
+            HistoricalFeatureOps that = (HistoricalFeatureOps) o;
+            //noinspection PointlessBooleanExpression
+            return true
+                    && Objects.equals(mFeatureId, that.mFeatureId)
+                    && Objects.equals(mHistoricalOps, that.mHistoricalOps);
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public int hashCode() {
+            // You can override field hashCode logic by defining methods like:
+            // int fieldNameHashCode() { ... }
+
+            int _hash = 1;
+            _hash = 31 * _hash + Objects.hashCode(mFeatureId);
+            _hash = 31 * _hash + Objects.hashCode(mHistoricalOps);
+            return _hash;
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            // You can override field parcelling by defining methods like:
+            // void parcelFieldName(Parcel dest, int flags) { ... }
+
+            byte flg = 0;
+            if (mFeatureId != null) flg |= 0x1;
+            if (mHistoricalOps != null) flg |= 0x2;
+            dest.writeByte(flg);
+            if (mFeatureId != null) dest.writeString(mFeatureId);
+            if (mHistoricalOps != null) dest.writeMap(mHistoricalOps);
+        }
+
+        @Override
+        @DataClass.Generated.Member
+        public int describeContents() { return 0; }
+
+        /** @hide */
+        @SuppressWarnings({"unchecked", "RedundantCast"})
+        @DataClass.Generated.Member
+        /* package-private */ HistoricalFeatureOps(@NonNull Parcel in) {
+            // You can override field unparcelling by defining methods like:
+            // static FieldType unparcelFieldName(Parcel in) { ... }
+
+            byte flg = in.readByte();
+            String featureId = (flg & 0x1) == 0 ? null : in.readString();
+            ArrayMap<String,HistoricalOp> historicalOps = null;
+            if ((flg & 0x2) != 0) {
+                historicalOps = new ArrayMap();
+                in.readMap(historicalOps, HistoricalOp.class.getClassLoader());
+            }
+
+            this.mFeatureId = featureId;
+            this.mHistoricalOps = historicalOps;
+
+            // onConstructed(); // You can define this method to get a callback
+        }
+
+        @DataClass.Generated.Member
+        public static final @NonNull Parcelable.Creator<HistoricalFeatureOps> CREATOR
+                = new Parcelable.Creator<HistoricalFeatureOps>() {
             @Override
-            public @NonNull HistoricalPackageOps createFromParcel(@NonNull Parcel parcel) {
-                return new HistoricalPackageOps(parcel);
+            public HistoricalFeatureOps[] newArray(int size) {
+                return new HistoricalFeatureOps[size];
             }
 
             @Override
-            public @NonNull HistoricalPackageOps[] newArray(int size) {
-                return new HistoricalPackageOps[size];
+            public HistoricalFeatureOps createFromParcel(@NonNull Parcel in) {
+                return new HistoricalFeatureOps(in);
             }
         };
 
-        @Override
-        public boolean equals(@Nullable Object obj) {
-            if (this == obj) {
-                return true;
-            }
-            if (obj == null || getClass() != obj.getClass()) {
-                return false;
-            }
-            final HistoricalPackageOps other = (HistoricalPackageOps) obj;
-            if (!mPackageName.equals(other.mPackageName)) {
-                return false;
-            }
-            if (mHistoricalOps == null) {
-                if (other.mHistoricalOps != null) {
-                    return false;
-                }
-            } else if (!mHistoricalOps.equals(other.mHistoricalOps)) {
-                return false;
-            }
-            return true;
-        }
+        /*
+        @DataClass.Generated(
+                time = 1578113234821L,
+                codegenVersion = "1.0.14",
+                sourceFile = "frameworks/base/core/java/android/app/AppOpsManager.java",
+                inputSignatures = "private final @android.annotation.Nullable java.lang.String mFeatureId\nprivate @android.annotation.Nullable android.util.ArrayMap<java.lang.String,android.app.HistoricalOp> mHistoricalOps\nprivate @android.annotation.Nullable android.app.HistoricalFeatureOps splice(double)\nprivate  void merge(android.app.HistoricalFeatureOps)\nprivate  void filter(java.lang.String[],int,double)\nprivate  boolean isEmpty()\nprivate  void increaseAccessCount(int,int,int,long)\nprivate  void increaseRejectCount(int,int,int,long)\nprivate  void increaseAccessDuration(int,int,int,long)\npublic @android.annotation.IntRange(from=0L) int getOpCount()\npublic @android.annotation.NonNull android.app.HistoricalOp getOpAt(int)\npublic @android.annotation.Nullable android.app.HistoricalOp getOp(java.lang.String)\nprivate  void accept(android.app.HistoricalOpsVisitor)\nprivate @android.annotation.NonNull android.app.HistoricalOp getOrCreateHistoricalOp(int)\nclass HistoricalFeatureOps extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genEqualsHashCode=true, genHiddenCopyConstructor=true)")
+        @Deprecated
+        private void __metadata() {}
+        */
 
-        @Override
-        public int hashCode() {
-            int result = mPackageName != null ? mPackageName.hashCode() : 0;
-            result = 31 * result + (mHistoricalOps != null ? mHistoricalOps.hashCode() : 0);
-            return result;
-        }
+        //@formatter:on
+        // End of generated code
+
     }
 
     /**
@@ -5288,13 +5789,13 @@
             if (mOp != other.mOp) {
                 return false;
             }
-            if (!Objects.equals(mAccessCount, other.mAccessCount)) {
+            if (!equalsLongSparseLongArray(mAccessCount, other.mAccessCount)) {
                 return false;
             }
-            if (!Objects.equals(mRejectCount, other.mRejectCount)) {
+            if (!equalsLongSparseLongArray(mRejectCount, other.mRejectCount)) {
                 return false;
             }
-            return Objects.equals(mAccessDuration, other.mAccessDuration);
+            return equalsLongSparseLongArray(mAccessDuration, other.mAccessDuration);
         }
 
         @Override
@@ -5621,9 +6122,9 @@
         Objects.requireNonNull(executor, "executor cannot be null");
         Objects.requireNonNull(callback, "callback cannot be null");
         try {
-            mService.getHistoricalOps(request.mUid, request.mPackageName, request.mOpNames,
-                    request.mBeginTimeMillis, request.mEndTimeMillis, request.mFlags,
-                    new RemoteCallback((result) -> {
+            mService.getHistoricalOps(request.mUid, request.mPackageName, request.mFeatureId,
+                    request.mOpNames, request.mFilter, request.mBeginTimeMillis,
+                    request.mEndTimeMillis, request.mFlags, new RemoteCallback((result) -> {
                 final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
                 final long identity = Binder.clearCallingIdentity();
                 try {
@@ -5661,8 +6162,8 @@
         Objects.requireNonNull(callback, "callback cannot be null");
         try {
             mService.getHistoricalOpsFromDiskRaw(request.mUid, request.mPackageName,
-                    request.mOpNames, request.mBeginTimeMillis, request.mEndTimeMillis,
-                    request.mFlags, new RemoteCallback((result) -> {
+                    request.mFeatureId, request.mOpNames, request.mFilter, request.mBeginTimeMillis,
+                    request.mEndTimeMillis, request.mFlags, new RemoteCallback((result) -> {
                 final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
                 final long identity = Binder.clearCallingIdentity();
                 try {
@@ -7503,6 +8004,30 @@
         return lastEvent;
     }
 
+    private static boolean equalsLongSparseLongArray(@Nullable LongSparseLongArray a,
+            @Nullable LongSparseLongArray b) {
+        if (a == b) {
+            return true;
+        }
+
+        if (a == null || b == null) {
+            return false;
+        }
+
+        if (a.size() != b.size()) {
+            return false;
+        }
+
+        int numEntries = a.size();
+        for (int i = 0; i < numEntries; i++) {
+            if (a.keyAt(i) != b.keyAt(i) || a.valueAt(i) != b.valueAt(i)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     private static void writeLongSparseLongArrayToParcel(
             @Nullable LongSparseLongArray array, @NonNull Parcel parcel) {
         if (array != null) {
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index 83d1de6..4e50a3f 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -390,10 +390,10 @@
     public byte[] getReports(long configKey) throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsd service = getIStatsdLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 return service.getData(configKey, mContext.getOpPackageName());
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsd when getting data");
+                Slog.e(TAG, "Failed to connect to statsmanager when getting data");
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
@@ -427,10 +427,10 @@
     public byte[] getStatsMetadata() throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsd service = getIStatsdLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 return service.getMetadata(mContext.getOpPackageName());
             } catch (RemoteException e) {
-                Slog.e(TAG, "Failed to connect to statsd when getting metadata");
+                Slog.e(TAG, "Failed to connect to statsmanager when getting metadata");
                 throw new StatsUnavailableException("could not connect", e);
             } catch (SecurityException e) {
                 throw new StatsUnavailableException(e.getMessage(), e);
@@ -462,21 +462,19 @@
             throws StatsUnavailableException {
         synchronized (sLock) {
             try {
-                IStatsd service = getIStatsdLocked();
+                IStatsManagerService service = getIStatsManagerServiceLocked();
                 if (service == null) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Failed to find statsd when getting experiment IDs");
-                    }
-                    return new long[0];
+                    throw new StatsUnavailableException("Failed to find statsmanager when "
+                                                              + "getting experiment IDs");
                 }
                 return service.getRegisteredExperimentIds();
             } catch (RemoteException e) {
                 if (DEBUG) {
                     Slog.d(TAG,
-                            "Failed to connect to StatsCompanionService when getting "
+                            "Failed to connect to StatsManagerService when getting "
                                     + "registered experiment IDs");
                 }
-                return new long[0];
+                throw new StatsUnavailableException("could not connect", e);
             }
         }
     }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index d3132d8..2aac94c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -88,7 +88,6 @@
 import android.util.ArraySet;
 import android.util.Log;
 
-import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.Preconditions;
@@ -6757,6 +6756,34 @@
     }
 
     /**
+     * @hide
+     * Privileged apps can use this method to find out if the device was provisioned as
+     * organization-owend device with a managed profile.
+     *
+     * This, together with checking whether the device has a device owner (by calling
+     * {@link #isDeviceManaged()}), could be used to learn whether the device is owned by an
+     * organization or an individual:
+     * If this method returns true OR {@link #isDeviceManaged()} returns true, then
+     * the device is owned by an organization. Otherwise, it's owned by an individual.
+     *
+     * @return {@code true} if the device was provisioned as organization-owned device,
+     * {@code false} otherwise.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean isOrganizationOwnedDeviceWithManagedProfile() {
+        throwIfParentInstance("isOrganizationOwnedDeviceWithManagedProfile");
+        if (mService != null) {
+            try {
+                return mService.isOrganizationOwnedDeviceWithManagedProfile();
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
      * Returns whether the specified package can read the device identifiers.
      *
      * @param packageName The package name of the app to check for device identifier access.
@@ -11215,12 +11242,12 @@
      * #setCrossProfilePackages(ComponentName, Set)}.</li>
      * <li>The default package names set by the OEM that are allowed to request user consent for
      * cross-profile communication without being explicitly enabled by the admin, via
-     * {@link R.array#cross_profile_apps}</li>
+     * {@link com.android.internal.R.array#cross_profile_apps}</li>
      * </ul>
      *
      * @return the combined set of whitelisted package names set via
      * {@link #setCrossProfilePackages(ComponentName, Set)} and
-     * {@link R.array#cross_profile_apps}
+     * {@link com.android.internal.R.array#cross_profile_apps}
      *
      * @hide
      */
@@ -11338,4 +11365,39 @@
         }
         return false;
     }
+
+    /**
+     * Called by Device owner to set packages as protected. User will not be able to clear app
+     * data or force-stop protected packages.
+     *
+     * @param admin which {@link DeviceAdminReceiver} this request is associated with
+     * @param packages The package names to protect.
+     * @throws SecurityException if {@code admin} is not a device owner.
+     */
+    public void setProtectedPackages(@NonNull ComponentName admin, @NonNull List<String> packages) {
+        if (mService != null) {
+            try {
+                mService.setProtectedPackages(admin, packages);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Returns the list of packages protected by the device owner.
+     *
+     * @param admin which {@link DeviceAdminReceiver} this request is associated with
+     * @throws SecurityException if {@code admin} is not a device owner.
+     */
+    public @NonNull List<String> getProtectedPackages(@NonNull ComponentName admin) {
+        if (mService != null) {
+            try {
+                return mService.getProtectedPackages(admin);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+        }
+        return Collections.emptyList();
+    }
 }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 08c5dff..3eec46b 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -156,6 +156,7 @@
     void setProfileName(in ComponentName who, String profileName);
     void clearProfileOwner(in ComponentName who);
     boolean hasUserSetupCompleted();
+    boolean isOrganizationOwnedDeviceWithManagedProfile();
 
     boolean checkDeviceIdentifierAccess(in String packageName, int pid, int uid);
 
@@ -452,4 +453,8 @@
     boolean startViewCalendarEventInManagedProfile(String packageName, long eventId, long start, long end, boolean allDay, int flags);
 
     boolean setKeyGrantForApp(in ComponentName admin, String callerPackage, String alias, String packageName, boolean hasGrant);
+
+    void setProtectedPackages(in ComponentName admin, in List<String> packages);
+
+    List<String> getProtectedPackages(in ComponentName admin);
 }
diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java
index 55f92be..50de7385 100644
--- a/core/java/android/app/timedetector/ManualTimeSuggestion.java
+++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java
@@ -20,7 +20,7 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/core/java/android/app/timedetector/NetworkTimeSuggestion.java b/core/java/android/app/timedetector/NetworkTimeSuggestion.java
index 4c55ba1..17e9c5a 100644
--- a/core/java/android/app/timedetector/NetworkTimeSuggestion.java
+++ b/core/java/android/app/timedetector/NetworkTimeSuggestion.java
@@ -20,7 +20,7 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
index 4a89a12..479e4b4 100644
--- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java
+++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
@@ -20,7 +20,7 @@
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index af9ece0..54dd1be 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -24,8 +24,8 @@
 import android.os.ServiceManager;
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.SystemClock;
+import android.os.TimestampedValue;
 import android.util.Log;
-import android.util.TimestampedValue;
 
 /**
  * The interface through which system components can send signals to the TimeDetectorService.
diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java
index 8f5cdf0..c1233b8 100644
--- a/core/java/android/bluetooth/BluetoothHidHost.java
+++ b/core/java/android/bluetooth/BluetoothHidHost.java
@@ -17,9 +17,12 @@
 package android.bluetooth;
 
 import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.Binder;
@@ -43,6 +46,7 @@
  *
  * @hide
  */
+@SystemApi
 public final class BluetoothHidHost implements BluetoothProfile {
     private static final String TAG = "BluetoothHidHost";
     private static final boolean DBG = true;
@@ -66,6 +70,7 @@
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
      * receive.
      */
+    @SuppressLint("ActionValue")
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
     public static final String ACTION_CONNECTION_STATE_CHANGED =
             "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
@@ -325,7 +330,7 @@
      * {@inheritDoc}
      */
     @Override
-    public List<BluetoothDevice> getConnectedDevices() {
+    public @NonNull List<BluetoothDevice> getConnectedDevices() {
         if (VDBG) log("getConnectedDevices()");
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled()) {
@@ -342,6 +347,8 @@
 
     /**
      * {@inheritDoc}
+     *
+     * @hide
      */
     @Override
     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
@@ -363,7 +370,7 @@
      * {@inheritDoc}
      */
     @Override
-    public int getConnectionState(BluetoothDevice device) {
+    public int getConnectionState(@Nullable BluetoothDevice device) {
         if (VDBG) log("getState(" + device + ")");
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
@@ -409,7 +416,7 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
-    public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+    public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) {
         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
@@ -457,7 +464,7 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.BLUETOOTH)
-    public int getConnectionPolicy(BluetoothDevice device) {
+    public int getConnectionPolicy(@Nullable BluetoothDevice device) {
         if (VDBG) log("getConnectionPolicy(" + device + ")");
         final IBluetoothHidHost service = getService();
         if (service != null && isEnabled() && isValidDevice(device)) {
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index c579fdf..948885e 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -16,7 +16,10 @@
 
 package android.bluetooth;
 
+import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -271,6 +274,42 @@
     }
 
     /**
+     * Pbap does not store connection policy, so this function only disconnects Pbap if
+     * connectionPolicy is CONNECTION_POLICY_FORBIDDEN.
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if pbap is successfully disconnected, false otherwise
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        try {
+            final IBluetoothPbap service = mService;
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                    return false;
+                }
+                return service.setConnectionPolicy(device, connectionPolicy);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
      * Disconnects the current Pbap client (PCE). Currently this call blocks,
      * it may soon be made asynchronous. Returns false if this proxy object is
      * not currently connected to the Pbap service.
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index 27960b0..1967a01 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -305,6 +305,12 @@
         proto.end(token);
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Two components are considered to be equal if the packages in which they reside have the
+     * same name, and if the classes that implement each component also have the same name.
+     */
     @Override
     public boolean equals(Object obj) {
         try {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4815d78..a28868e 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3992,6 +3992,7 @@
      */
     public static final String NETWORK_STATS_SERVICE = "netstats";
     /** {@hide} */
+    @SystemApi
     public static final String NETWORK_POLICY_SERVICE = "netpolicy";
     /** {@hide} */
     public static final String NETWORK_WATCHLIST_SERVICE = "network_watchlist";
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f792127..b85c58a 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2918,6 +2918,18 @@
     public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+     * the requisite hardware support to support reboot escrow of synthetic password for updates.
+     *
+     * <p>This feature implies that the device has the RebootEscrow HAL implementation.
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
+
+    /**
      * Extra field name for the URI to a verification file. Passed to a package
      * verifier.
      *
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index b808088..55505ba 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -47,8 +47,7 @@
 import java.util.UUID;
 
 /**
- * The SoundTrigger class provides access via JNI to the native service managing
- * the sound trigger HAL.
+ * The SoundTrigger class provides access to the service managing the sound trigger HAL.
  *
  * @hide
  */
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 431773d..9731f3c 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -247,9 +247,8 @@
          * removing even the capabilities that are set by default when the object is constructed.
          *
          * @return The builder to facilitate chaining.
-         * @hide
          */
-        @UnsupportedAppUsage
+        @NonNull
         public Builder clearCapabilities() {
             mNetworkCapabilities.clearAll();
             return this;
diff --git a/core/java/android/net/NetworkSpecifier.java b/core/java/android/net/NetworkSpecifier.java
index 2bc3eb5..cf31d21 100644
--- a/core/java/android/net/NetworkSpecifier.java
+++ b/core/java/android/net/NetworkSpecifier.java
@@ -16,6 +16,9 @@
 
 package android.net;
 
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
 /**
  * Describes specific properties of a requested network for use in a {@link NetworkRequest}.
  *
@@ -31,7 +34,8 @@
      *
      * @hide
      */
-    public abstract boolean satisfiedBy(NetworkSpecifier other);
+    @SystemApi
+    public abstract boolean satisfiedBy(@Nullable NetworkSpecifier other);
 
     /**
      * Optional method which can be overridden by concrete implementations of NetworkSpecifier to
@@ -45,6 +49,7 @@
      *
      * @hide
      */
+    @SystemApi
     public void assertValidFromUid(int requestorUid) {
         // empty
     }
@@ -68,6 +73,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @Nullable
     public NetworkSpecifier redact() {
         // TODO (b/122160111): convert default to null once all platform NetworkSpecifiers
         // implement this method.
diff --git a/core/java/android/os/ExternalVibration.java b/core/java/android/os/ExternalVibration.java
index 2991db2..e62244f 100644
--- a/core/java/android/os/ExternalVibration.java
+++ b/core/java/android/os/ExternalVibration.java
@@ -161,7 +161,6 @@
         out.writeInt(mUid);
         out.writeString(mPkg);
         writeAudioAttributes(mAttrs, out, flags);
-        out.writeParcelable(mAttrs, flags);
         out.writeStrongBinder(mController.asBinder());
         out.writeStrongBinder(mToken);
     }
diff --git a/core/java/android/os/IRecoverySystem.aidl b/core/java/android/os/IRecoverySystem.aidl
index c5ceecd..2561e1e 100644
--- a/core/java/android/os/IRecoverySystem.aidl
+++ b/core/java/android/os/IRecoverySystem.aidl
@@ -17,6 +17,7 @@
 
 package android.os;
 
+import android.content.IntentSender;
 import android.os.IRecoverySystemProgressListener;
 
 /** @hide */
@@ -26,4 +27,7 @@
     boolean setupBcb(in String command);
     boolean clearBcb();
     void rebootRecoveryWithCommand(in String command);
+    boolean requestLskf(in String updateToken, in IntentSender sender);
+    boolean clearLskf();
+    boolean rebootWithLskf(in String updateToken, in String reason);
 }
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index e81a505..edaaf81 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -62,7 +62,7 @@
     boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne);
     UserInfo getProfileParent(int userId);
     boolean isSameProfileGroup(int userId, int otherUserHandle);
-    String getUserTypeForUser(int userId);
+    boolean isUserOfType(int userId, in String userType);
     @UnsupportedAppUsage
     UserInfo getUserInfo(int userId);
     String getUserAccount(int userId);
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 1901820..cdcb3ff 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -18,6 +18,8 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
@@ -29,6 +31,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.IntentSender;
 import android.content.pm.PackageManager;
 import android.provider.Settings;
 import android.telephony.SubscriptionInfo;
@@ -624,22 +627,91 @@
     }
 
     /**
-     * Schedule to install the given package on next boot. The caller needs to
-     * ensure that the package must have been processed (uncrypt'd) if needed.
-     * It sets up the command in BCB (bootloader control block), which will
-     * be read by the bootloader and the recovery image.
+     * Prepare to apply an unattended update by asking the user for their Lock Screen Knowledge
+     * Factor (LSKF). If supplied, the {@code intentSender} will be called when the system is setup
+     * and ready to apply the OTA.
+     * <p>
+     * When the system is already prepared for update and this API is called again with the same
+     * {@code updateToken}, it will not call the intent sender nor request the user enter their Lock
+     * Screen Knowledge Factor.
+     * <p>
+     * When this API is called again with a different {@code updateToken}, the prepared-for-update
+     * status is reset and process repeats as though it's the initial call to this method as
+     * described in the first paragraph.
      *
-     * @param Context      the Context to use.
-     * @param packageFile  the package to be installed.
-     *
-     * @throws IOException if there were any errors setting up the BCB.
-     *
+     * @param context the Context to use.
+     * @param updateToken token used to indicate which update was prepared
+     * @param intentSender the intent to call when the update is prepared; may be {@code null}
+     * @throws IOException if there were any errors setting up unattended update
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.RECOVERY)
-    public static void scheduleUpdateOnBoot(Context context, File packageFile)
+    public static void prepareForUnattendedUpdate(@NonNull Context context,
+            @NonNull String updateToken, @Nullable IntentSender intentSender) throws IOException {
+        if (updateToken == null) {
+            throw new NullPointerException("updateToken == null");
+        }
+        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+        if (!rs.requestLskf(updateToken, intentSender)) {
+            throw new IOException("preparation for update failed");
+        }
+    }
+
+    /**
+     * Request that any previously requested Lock Screen Knowledge Factor (LSKF) is cleared and
+     * the preparation for unattended update is reset.
+     *
+     * @param context the Context to use.
+     * @throws IOException if there were any errors setting up unattended update
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.RECOVERY)
+    public static boolean clearPrepareForUnattendedUpdate(@NonNull Context context)
             throws IOException {
+        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+        return rs.clearLskf();
+    }
+
+    /**
+     * Request that the device reboot and apply the update that has been prepared. The
+     * {@code updateToken} must match what was given for {@link #prepareForUnattendedUpdate} or
+     * this will return {@code false}.
+     *
+     * @param context the Context to use.
+     * @param updateToken the token used to call {@link #prepareForUnattendedUpdate} before
+     * @param reason the reboot reason to give to the {@link PowerManager}
+     * @throws IOException if there were any errors setting up unattended update
+     * @return false if the reboot couldn't proceed because the device wasn't ready for an
+     *               unattended reboot or if the {@code updateToken} did not match the previously
+     *               given token
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.RECOVERY)
+    public static boolean rebootAndApply(@NonNull Context context, @NonNull String updateToken,
+            @NonNull String reason) throws IOException {
+        if (updateToken == null) {
+            throw new NullPointerException("updateToken == null");
+        }
+        RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+        return rs.rebootWithLskf(updateToken, reason);
+    }
+
+    /**
+     * Schedule to install the given package on next boot. The caller needs to ensure that the
+     * package must have been processed (uncrypt'd) if needed. It sets up the command in BCB
+     * (bootloader control block), which will be read by the bootloader and the recovery image.
+     *
+     * @param context the Context to use.
+     * @param packageFile the package to be installed.
+     * @throws IOException if there were any errors setting up the BCB.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.RECOVERY)
+    public static void scheduleUpdateOnBoot(Context context, File packageFile) throws IOException {
         String filename = packageFile.getCanonicalPath();
         boolean securityUpdate = filename.endsWith("_s.zip");
 
@@ -1204,6 +1276,49 @@
     }
 
     /**
+     * Begins the process of asking the user for the Lock Screen Knowledge Factor.
+     *
+     * @param updateToken token that will be used in calls to {@link #rebootAndApply} to ensure
+     *                    that the preparation was for the correct update
+     * @return true if the request was correct
+     * @throws IOException if the recovery system service could not be contacted
+     */
+    private boolean requestLskf(String updateToken, IntentSender sender) throws IOException {
+        try {
+            return mService.requestLskf(updateToken, sender);
+        } catch (RemoteException e) {
+            throw new IOException("could request update");
+        }
+    }
+
+    /**
+     * Calls the recovery system service and clears the setup for the OTA.
+     *
+     * @return true if the setup for OTA was cleared
+     * @throws IOException if the recovery system service could not be contacted
+     */
+    private boolean clearLskf() throws IOException {
+        try {
+            return mService.clearLskf();
+        } catch (RemoteException e) {
+            throw new IOException("could not clear LSKF");
+        }
+    }
+
+    /**
+     * Calls the recovery system service to reboot and apply update.
+     *
+     * @param updateToken the update token for which the update was prepared
+     */
+    private boolean rebootWithLskf(String updateToken, String reason) throws IOException {
+        try {
+            return mService.rebootWithLskf(updateToken, reason);
+        } catch (RemoteException e) {
+            throw new IOException("could not reboot for update");
+        }
+    }
+
+    /**
      * Internally, recovery treats each line of the command file as a separate
      * argv, so we only need to protect against newlines and nulls.
      */
diff --git a/core/java/android/util/TimestampedValue.java b/core/java/android/os/TimestampedValue.java
similarity index 97%
rename from core/java/android/util/TimestampedValue.java
rename to core/java/android/os/TimestampedValue.java
index 4505673..348574e 100644
--- a/core/java/android/util/TimestampedValue.java
+++ b/core/java/android/os/TimestampedValue.java
@@ -14,13 +14,10 @@
  * limitations under the License.
  */
 
-package android.util;
+package android.os;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.SystemClock;
 
 import java.util.Objects;
 
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index a9ddffe..16f7b39 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.IUpdateEngine;
@@ -140,8 +141,43 @@
          * {@code SWITCH_SLOT_ON_REBOOT=0}. See {@link #applyPayload}.
          */
         public static final int UPDATED_BUT_NOT_ACTIVE = 52;
+
+        /**
+         * Error code: there is not enough space on the device to apply the update. User should
+         * be prompted to free up space and re-try the update.
+         *
+         * <p>See {@link UpdateEngine#allocateSpace}.
+         */
+        public static final int NOT_ENOUGH_SPACE = 60;
+
+        /**
+         * Error code: the device is corrupted and no further updates may be applied.
+         *
+         * <p>See {@link UpdateEngine#cleanupAppliedPayload}.
+         */
+        public static final int DEVICE_CORRUPTED = 61;
     }
 
+    /** @hide */
+    @IntDef(value = {
+            ErrorCodeConstants.SUCCESS,
+            ErrorCodeConstants.ERROR,
+            ErrorCodeConstants.FILESYSTEM_COPIER_ERROR,
+            ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
+            ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
+            ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
+            ErrorCodeConstants.KERNEL_DEVICE_OPEN_ERROR,
+            ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
+            ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR,
+            ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
+            ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
+            ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR,
+            ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
+            ErrorCodeConstants.NOT_ENOUGH_SPACE,
+            ErrorCodeConstants.DEVICE_CORRUPTED,
+    })
+    public @interface ErrorCode {}
+
     /**
      * Status codes for update engine. Values must agree with the ones in
      * {@code system/update_engine/client_library/include/update_engine/update_status.h}.
@@ -419,4 +455,138 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Return value of {@link #allocateSpace.}
+     */
+    public static final class AllocateSpaceResult {
+        private @ErrorCode int mErrorCode = ErrorCodeConstants.SUCCESS;
+        private long mFreeSpaceRequired = 0;
+        private AllocateSpaceResult() {}
+        /**
+         * Error code.
+         *
+         * @return The following error codes:
+         * <ul>
+         * <li>{@link ErrorCodeConstants#SUCCESS} if space has been allocated
+         *         successfully.</li>
+         * <li>{@link ErrorCodeConstants#NOT_ENOUGH_SPACE} if insufficient
+         *         space.</li>
+         * <li>Other {@link ErrorCodeConstants} for other errors.</li>
+         * </ul>
+         */
+        @ErrorCode
+        public int errorCode() {
+            return mErrorCode;
+        }
+
+        /**
+         * Estimated total space that needs to be available on the userdata partition to apply the
+         * payload (in bytes).
+         *
+         * <p>
+         * Note that in practice, more space needs to be made available before applying the payload
+         * to keep the device working.
+         *
+         * @return The following values:
+         * <ul>
+         * <li>zero if {@link #errorCode} returns {@link ErrorCodeConstants#SUCCESS}</li>
+         * <li>non-zero if {@link #errorCode} returns {@link ErrorCodeConstants#NOT_ENOUGH_SPACE}.
+         * Value is the estimated total space required on userdata partition.</li>
+         * </ul>
+         * @throws IllegalStateException if {@link #errorCode} is not one of the above.
+         *
+         */
+        public long freeSpaceRequired() {
+            if (mErrorCode == ErrorCodeConstants.SUCCESS) {
+                return 0;
+            }
+            if (mErrorCode == ErrorCodeConstants.NOT_ENOUGH_SPACE) {
+                return mFreeSpaceRequired;
+            }
+            throw new IllegalStateException(String.format(
+                    "freeSpaceRequired() is not available when error code is %d", mErrorCode));
+        }
+    }
+
+    /**
+     * Initialize partitions for a payload associated with the given payload
+     * metadata {@code payloadMetadataFilename} by preallocating required space.
+     *
+     * <p>This function should be called after payload has been verified after
+     * {@link #verifyPayloadMetadata}. This function does not verify whether
+     * the given payload is applicable or not.
+     *
+     * <p>Implementation of {@code allocateSpace} uses
+     * {@code headerKeyValuePairs} to determine whether space has been allocated
+     * for a different or same payload previously. If space has been allocated
+     * for a different payload before, space will be reallocated for the given
+     * payload. If space has been allocated for the same payload, no actions to
+     * storage devices are taken.
+     *
+     * <p>This function is synchronous and may take a non-trivial amount of
+     * time. Callers should call this function in a background thread.
+     *
+     * @param payloadMetadataFilename See {@link #verifyPayloadMetadata}.
+     * @param headerKeyValuePairs See {@link #applyPayload}.
+     * @return See {@link AllocateSpaceResult}.
+     */
+    @NonNull
+    public AllocateSpaceResult allocateSpace(
+                @NonNull String payloadMetadataFilename,
+                @NonNull String[] headerKeyValuePairs) {
+        AllocateSpaceResult result = new AllocateSpaceResult();
+        try {
+            result.mFreeSpaceRequired = mUpdateEngine.allocateSpaceForPayload(
+                    payloadMetadataFilename,
+                    headerKeyValuePairs);
+            result.mErrorCode = result.mFreeSpaceRequired == 0
+                    ? ErrorCodeConstants.SUCCESS
+                    : ErrorCodeConstants.NOT_ENOUGH_SPACE;
+            return result;
+        } catch (ServiceSpecificException e) {
+            result.mErrorCode = e.errorCode;
+            result.mFreeSpaceRequired = 0;
+            return result;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Cleanup files used by the previous update and free up space after the
+     * device has been booted successfully into the new build.
+     *
+     * <p>In particular, this function waits until delta files for snapshots for
+     * Virtual A/B update are merged to OS partitions, then delete these delta
+     * files.
+     *
+     * <p>This function is synchronous and may take a non-trivial amount of
+     * time. Callers should call this function in a background thread.
+     *
+     * <p>This function does not delete payload binaries downloaded for a
+     * non-streaming OTA update.
+     *
+     * @return One of the following:
+     * <ul>
+     * <li>{@link ErrorCodeConstants#SUCCESS} if execution is successful.</li>
+     * <li>{@link ErrorCodeConstants#ERROR} if a transient error has occurred.
+     * The device should be able to recover after a reboot. The function should
+     * be retried after the reboot.</li>
+     * <li>{@link ErrorCodeConstants#DEVICE_CORRUPTED} if a permanent error is
+     * encountered. Device is corrupted, and future updates must not be applied.
+     * The device cannot recover without flashing and factory resets.
+     * </ul>
+     *
+     * @throws ServiceSpecificException if other transient errors has occurred.
+     * A reboot may or may not help resolving the issue.
+     */
+    @ErrorCode
+    public int cleanupAppliedPayload() {
+        try {
+            return mUpdateEngine.cleanupSuccessfulUpdate();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/os/UpdateEngineCallback.java b/core/java/android/os/UpdateEngineCallback.java
index f07294e..7fe7024 100644
--- a/core/java/android/os/UpdateEngineCallback.java
+++ b/core/java/android/os/UpdateEngineCallback.java
@@ -44,5 +44,6 @@
      * unsuccessfully. The value of {@code errorCode} will be one of the
      * values from {@link UpdateEngine.ErrorCodeConstants}.
      */
-    public abstract void onPayloadApplicationComplete(int errorCode);
+    public abstract void onPayloadApplicationComplete(
+            @UpdateEngine.ErrorCode int errorCode);
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index fa9569b..6e199ce3 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1626,39 +1626,35 @@
     }
 
     /**
-     * Returns the calling user's user type.
+     * Returns whether the current user is of the given user type, such as
+     * {@link UserManager#USER_TYPE_FULL_GUEST}.
      *
-     * // TODO(b/142482943): Decide on the appropriate permission requirements.
-     *
-     * @return the name of the user type, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
+     * @return true if the user is of the given user type.
      * @hide
      */
-    public @NonNull String getUserType() {
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean isUserOfType(@NonNull String userType) {
         try {
-            return mService.getUserTypeForUser(UserHandle.myUserId());
+            return mService.isUserOfType(UserHandle.myUserId(), userType);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Returns the given user's user type.
-     *
-     * // TODO(b/142482943): Decide on the appropriate permission requirements.
-     * Requires {@link android.Manifest.permission#MANAGE_USERS} or
-     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller
-     * must be in the same profile group of specified user.
+     * Returns whether the given user is of the given user type, such as
+     * {@link UserManager#USER_TYPE_FULL_GUEST}.
      *
      * @param userHandle the user handle of the user whose type is being requested.
-     * @return the name of the user's user type, e.g. {@link UserManager#USER_TYPE_PROFILE_MANAGED},
-     *         or {@code null} if there is no such user.
+     * @param userType the name of the user's user type, e.g.
+     *                 {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
+     * @return true if the userHandle user is of type userType
      * @hide
      */
-    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
-            android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
-    public @Nullable String getUserTypeForUser(@NonNull UserHandle userHandle) {
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean isUserOfType(@NonNull UserHandle userHandle, @NonNull String userType) {
         try {
-            return mService.getUserTypeForUser(userHandle.getIdentifier());
+            return mService.isUserOfType(userHandle.getIdentifier(), userType);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index b94475a..9387a2c 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -24,6 +24,7 @@
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.telephony.Annotation.ApnType;
 import android.telephony.Annotation.CallState;
 import android.telephony.Annotation.DataActivityType;
 import android.telephony.Annotation.DataFailureCause;
@@ -352,7 +353,7 @@
      * @param subId for which data connection state changed.
      * @param slotIndex for which data connections state changed. Can be derived from subId except
      * when subId is invalid.
-     * @param apnType the APN type that triggered this update
+     * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags.
      * @param preciseState the PreciseDataConnectionState
      *
      * @see android.telephony.PreciseDataConnection
@@ -360,7 +361,7 @@
      * @hide
      */
     public void notifyDataConnectionForSubscriber(int slotIndex, int subId,
-            String apnType, PreciseDataConnectionState preciseState) {
+            @ApnType int apnType, PreciseDataConnectionState preciseState) {
         try {
             sRegistry.notifyDataConnectionForSubscriber(
                     slotIndex, subId, apnType, preciseState);
@@ -553,13 +554,13 @@
      * @param subId for which data connection failed.
      * @param slotIndex for which data conenction failed. Can be derived from subId except when
      * subId is invalid.
-     * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN.
+     * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags.
      * @param apn the APN {@link ApnSetting#getApnName()} of this data connection.
      * @param failCause data fail cause.
      *
      * @hide
      */
-    public void notifyPreciseDataConnectionFailed(int subId, int slotIndex, String apnType,
+    public void notifyPreciseDataConnectionFailed(int subId, int slotIndex, @ApnType int apnType,
         String apn, @DataFailureCause int failCause) {
         try {
             sRegistry.notifyPreciseDataConnectionFailed(slotIndex, subId, apnType, apn, failCause);
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 3f679bba..f324113 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -387,6 +387,26 @@
     public static native int println_native(int bufID, int priority, String tag, String msg);
 
     /**
+     * Send a log message to the "radio" log buffer, which can be dumped with
+     * {@code adb logcat -b radio}.
+     *
+     * <p>Only the telephony mainline module should use it.
+     *
+     * <p>Note ART will protect {@code MODULE_LIBRARIES} system APIs from regular app code.
+     *
+     * @param priority Log priority.
+     * @param tag Used to identify the source of a log message.  It usually identifies
+     *        the class or activity where the log call occurs.
+     * @param message The message you would like logged.
+     * @hide
+     */
+    // @SystemApi(client= SystemApi.Client.MODULE_LIBRARIES) // TODO Uncomment once http://ag/9956147 is in.
+    public static int logToRadioBuffer(@Level int priority, @Nullable String tag,
+            @Nullable String message) {
+        return println_native(LOG_ID_RADIO, priority, tag, message);
+    }
+
+    /**
      * Return the maximum payload the log daemon accepts without truncation.
      * @return LOGGER_ENTRY_MAX_PAYLOAD.
      */
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index 2223d14..fa994ba 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -25,6 +25,7 @@
 import android.net.NetworkInfo;
 import android.net.SntpClient;
 import android.os.SystemClock;
+import android.os.TimestampedValue;
 import android.provider.Settings;
 import android.text.TextUtils;
 
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 95522ba..a0cf534 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1462,7 +1462,11 @@
         return false;
     }
 
-    void onTouchEvent(MotionEvent event) {
+    /**
+     * Handles touch events on an editable text view, implementing cursor movement, selection, etc.
+     */
+    @VisibleForTesting
+    public void onTouchEvent(MotionEvent event) {
         final boolean filterOutEvent = shouldFilterOutTouchEvent(event);
         mLastButtonState = event.getButtonState();
         if (filterOutEvent) {
@@ -2424,7 +2428,9 @@
         return mSelectionControllerEnabled;
     }
 
-    private InsertionPointCursorController getInsertionController() {
+    /** Returns the controller for the insertion cursor. */
+    @VisibleForTesting
+    public @Nullable InsertionPointCursorController getInsertionController() {
         if (!mInsertionControllerEnabled) {
             return null;
         }
@@ -2439,8 +2445,9 @@
         return mInsertionPointCursorController;
     }
 
-    @Nullable
-    SelectionModifierCursorController getSelectionController() {
+    /** Returns the controller for selection. */
+    @VisibleForTesting
+    public @Nullable SelectionModifierCursorController getSelectionController() {
         if (!mSelectionControllerEnabled) {
             return null;
         }
@@ -5723,11 +5730,16 @@
         }
     }
 
-    class InsertionPointCursorController implements CursorController {
+    /** Controller for the insertion cursor. */
+    @VisibleForTesting
+    public class InsertionPointCursorController implements CursorController {
         private InsertionHandleView mHandle;
         private boolean mIsDraggingCursor;
 
         public void onTouchEvent(MotionEvent event) {
+            if (getSelectionController().isCursorBeingModified()) {
+                return;
+            }
             switch (event.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN:
                     mIsDraggingCursor = false;
@@ -5900,7 +5912,9 @@
         }
     }
 
-    class SelectionModifierCursorController implements CursorController {
+    /** Controller for selection. */
+    @VisibleForTesting
+    public class SelectionModifierCursorController implements CursorController {
         // The cursor controller handles, lazily created when shown.
         private SelectionHandleView mStartHandle;
         private SelectionHandleView mEndHandle;
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 4e764fc..dabaf5a 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -58,10 +58,12 @@
     List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
     @UnsupportedAppUsage
     List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
-    void getHistoricalOps(int uid, String packageName, in List<String> ops, long beginTimeMillis,
-            long endTimeMillis, int flags, in RemoteCallback callback);
-    void getHistoricalOpsFromDiskRaw(int uid, String packageName, in List<String> ops,
-            long beginTimeMillis, long endTimeMillis, int flags, in RemoteCallback callback);
+    void getHistoricalOps(int uid, String packageName, String featureId, in List<String> ops,
+            int filter, long beginTimeMillis, long endTimeMillis, int flags,
+            in RemoteCallback callback);
+    void getHistoricalOpsFromDiskRaw(int uid, String packageName, String featureId,
+            in List<String> ops, int filter, long beginTimeMillis, long endTimeMillis, int flags,
+            in RemoteCallback callback);
     void offsetHistory(long duration);
     void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep);
     void addHistoricalOps(in AppOpsManager.HistoricalOps ops);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java b/core/java/com/android/internal/compat/AndroidBuildClassifier.java
similarity index 62%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
copy to core/java/com/android/internal/compat/AndroidBuildClassifier.java
index 155a6d2..0b937fa 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
+++ b/core/java/com/android/internal/compat/AndroidBuildClassifier.java
@@ -14,17 +14,21 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dagger.qualifiers;
+package com.android.internal.compat;
 
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import android.os.Build;
 
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
+/**
+ * Platform private class for determining the type of Android build installed.
+ *
+ */
+public class AndroidBuildClassifier {
 
-import javax.inject.Qualifier;
+    public boolean isDebuggableBuild() {
+        return Build.IS_DEBUGGABLE;
+    }
 
-@Qualifier
-@Documented
-@Retention(RUNTIME)
-public @interface DisplayId {
+    public boolean isFinalBuild() {
+        return "REL".equals(Build.VERSION.CODENAME);
+    }
 }
diff --git a/core/java/com/android/internal/compat/IOverrideValidator.aidl b/core/java/com/android/internal/compat/IOverrideValidator.aidl
new file mode 100644
index 0000000..add4708
--- /dev/null
+++ b/core/java/com/android/internal/compat/IOverrideValidator.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.compat;
+
+import android.content.pm.ApplicationInfo;
+
+import com.android.internal.compat.OverrideAllowedState;
+
+/**
+ * Platform private API for determining whether a changeId can be overridden.
+ *
+ * {@hide}
+ */
+interface IOverrideValidator
+{
+    /**
+     * Validation function.
+     * @param changeId    id of the change to be toggled on or off.
+     * @param packageName package of the app for which the change should be overridden.
+     * @return {@link OverrideAllowedState} specifying whether the change can be overridden for
+     * the given package or a reason why not.
+     */
+    OverrideAllowedState getOverrideAllowedState(long changeId, String packageName);
+}
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 7dcb12c..4c203d3 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.compat;
 
 import android.content.pm.ApplicationInfo;
+import com.android.internal.compat.IOverrideValidator;
 import java.util.Map;
 
 parcelable CompatibilityChangeConfig;
@@ -195,4 +196,9 @@
      * @return An array of {@link CompatChangeInfo} known to the service.
      */
     CompatibilityChangeInfo[] listAllChanges();
+
+    /**
+     * Get an instance that can determine whether a changeid can be overridden for a package name.
+     */
+    IOverrideValidator getOverrideValidator();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java b/core/java/com/android/internal/compat/OverrideAllowedState.aidl
similarity index 67%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
copy to core/java/com/android/internal/compat/OverrideAllowedState.aidl
index 155a6d2..10ceac7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
+++ b/core/java/com/android/internal/compat/OverrideAllowedState.aidl
@@ -14,17 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dagger.qualifiers;
+package com.android.internal.compat;
 
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Qualifier;
-
-@Qualifier
-@Documented
-@Retention(RUNTIME)
-public @interface DisplayId {
-}
+parcelable OverrideAllowedState;
\ No newline at end of file
diff --git a/core/java/com/android/internal/compat/OverrideAllowedState.java b/core/java/com/android/internal/compat/OverrideAllowedState.java
new file mode 100644
index 0000000..56216c2
--- /dev/null
+++ b/core/java/com/android/internal/compat/OverrideAllowedState.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.compat;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class contains all the possible override allowed states.
+ */
+public final class OverrideAllowedState implements Parcelable {
+    @IntDef({
+            ALLOWED,
+            DISABLED_NOT_DEBUGGABLE,
+            DISABLED_NON_TARGET_SDK,
+            DISABLED_TARGET_SDK_TOO_HIGH,
+            PACKAGE_DOES_NOT_EXIST
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface State {
+    }
+
+    /**
+     * Change can be overridden.
+     */
+    public static final int ALLOWED = 0;
+    /**
+     * Change cannot be overridden, due to the app not being debuggable.
+     */
+    public static final int DISABLED_NOT_DEBUGGABLE = 1;
+    /**
+     * Change cannot be overridden, due to the build being non-debuggable and the change being
+     * non-targetSdk.
+     */
+    public static final int DISABLED_NON_TARGET_SDK = 2;
+    /**
+     * Change cannot be overridden, due to the app's targetSdk being above the change's targetSdk.
+     */
+    public static final int DISABLED_TARGET_SDK_TOO_HIGH = 3;
+    /**
+     * Package does not exist.
+     */
+    public static final int PACKAGE_DOES_NOT_EXIST = 4;
+
+    @State
+    public final int state;
+    public final int appTargetSdk;
+    public final int changeIdTargetSdk;
+
+    private OverrideAllowedState(Parcel parcel) {
+        state = parcel.readInt();
+        appTargetSdk = parcel.readInt();
+        changeIdTargetSdk = parcel.readInt();
+    }
+
+    public OverrideAllowedState(@State int state, int appTargetSdk, int changeIdTargetSdk) {
+        this.state = state;
+        this.appTargetSdk = appTargetSdk;
+        this.changeIdTargetSdk = changeIdTargetSdk;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(state);
+        out.writeInt(appTargetSdk);
+        out.writeInt(changeIdTargetSdk);
+    }
+
+    /**
+     * Enforces the policy for overriding compat changes.
+     *
+     * @param changeId    the change id that was attempted to be overridden.
+     * @param packageName the package for which the attempt was made.
+     * @throws SecurityException if the policy forbids this operation.
+     */
+    public void enforce(long changeId, String packageName)
+            throws SecurityException {
+        switch (state) {
+            case ALLOWED:
+                return;
+            case DISABLED_NOT_DEBUGGABLE:
+                throw new SecurityException(
+                        "Cannot override a change on a non-debuggable app and user build.");
+            case DISABLED_NON_TARGET_SDK:
+                throw new SecurityException(
+                        "Cannot override a default enabled/disabled change on a user build.");
+            case DISABLED_TARGET_SDK_TOO_HIGH:
+                throw new SecurityException(String.format(
+                        "Cannot override %1$d for %2$s because the app's targetSdk (%3$d) is "
+                                + "above the change's targetSdk threshold (%4$d)",
+                        changeId, packageName, appTargetSdk, changeIdTargetSdk));
+            case PACKAGE_DOES_NOT_EXIST:
+                throw new SecurityException(String.format(
+                        "Cannot override %1$d for %2$s because the package does not exist, and "
+                                + "the change is targetSdk gated.",
+                        changeId, packageName));
+        }
+    }
+
+    public static final @NonNull
+            Parcelable.Creator<OverrideAllowedState> CREATOR =
+                new Parcelable.Creator<OverrideAllowedState>() {
+                public OverrideAllowedState createFromParcel(Parcel parcel) {
+                    OverrideAllowedState info = new OverrideAllowedState(parcel);
+                    return info;
+                }
+
+                public OverrideAllowedState[] newArray(int size) {
+                    return new OverrideAllowedState[size];
+                }
+            };
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (!(obj instanceof OverrideAllowedState)) {
+            return false;
+        }
+        OverrideAllowedState otherState = (OverrideAllowedState) obj;
+        return state == otherState.state
+                && appTargetSdk == otherState.appTargetSdk
+                && changeIdTargetSdk == otherState.changeIdTargetSdk;
+    }
+}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index a86d702..179828c 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -35,7 +35,6 @@
 import com.android.server.NetworkManagementSocketTagger;
 
 import dalvik.system.RuntimeHooks;
-import dalvik.system.ThreadPrioritySetter;
 import dalvik.system.VMRuntime;
 
 import libcore.content.type.MimeMap;
@@ -205,7 +204,6 @@
      */
     public static void preForkInit() {
         if (DEBUG) Slog.d(TAG, "Entered preForkInit.");
-        RuntimeHooks.setThreadPrioritySetter(new RuntimeThreadPrioritySetter());
         RuntimeInit.enableDdms();
         // TODO(b/142019040#comment13): Decide whether to load the default instance eagerly, i.e.
         // MimeMap.setDefault(DefaultMimeMapFactory.create());
@@ -218,35 +216,6 @@
         MimeMap.setDefaultSupplier(DefaultMimeMapFactory::create);
     }
 
-    private static class RuntimeThreadPrioritySetter implements ThreadPrioritySetter {
-        // Should remain consistent with kNiceValues[] in system/libartpalette/palette_android.cc
-        private static final int[] NICE_VALUES = {
-            Process.THREAD_PRIORITY_LOWEST,  // 1 (MIN_PRIORITY)
-            Process.THREAD_PRIORITY_BACKGROUND + 6,
-            Process.THREAD_PRIORITY_BACKGROUND + 3,
-            Process.THREAD_PRIORITY_BACKGROUND,
-            Process.THREAD_PRIORITY_DEFAULT,  // 5 (NORM_PRIORITY)
-            Process.THREAD_PRIORITY_DEFAULT - 2,
-            Process.THREAD_PRIORITY_DEFAULT - 4,
-            Process.THREAD_PRIORITY_URGENT_DISPLAY + 3,
-            Process.THREAD_PRIORITY_URGENT_DISPLAY + 2,
-            Process.THREAD_PRIORITY_URGENT_DISPLAY  // 10 (MAX_PRIORITY)
-        };
-
-        @Override
-        public void setPriority(int priority) {
-            // Check NICE_VALUES[] length first.
-            if (NICE_VALUES.length != (1 + Thread.MAX_PRIORITY - Thread.MIN_PRIORITY)) {
-                throw new AssertionError("Unexpected NICE_VALUES.length=" + NICE_VALUES.length);
-            }
-            // Priority should be in the range of MIN_PRIORITY (1) to MAX_PRIORITY (10).
-            if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
-                throw new IllegalArgumentException("Priority out of range: " + priority);
-            }
-            Process.setThreadPriority(NICE_VALUES[priority - Thread.MIN_PRIORITY]);
-        }
-    }
-
     @UnsupportedAppUsage
     protected static final void commonInit() {
         if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 6933f16..8e97ae1 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -63,7 +63,7 @@
     void notifyDataActivity(int state);
     void notifyDataActivityForSubscriber(in int subId, int state);
     void notifyDataConnectionForSubscriber(
-            int phoneId, int subId, String apnType, in PreciseDataConnectionState preciseState);
+            int phoneId, int subId, int apnType, in PreciseDataConnectionState preciseState);
     @UnsupportedAppUsage
     void notifyDataConnectionFailed(String apnType);
     // Uses CellIdentity which is Parcelable here; will convert to CellLocation in client.
@@ -75,7 +75,7 @@
             int foregroundCallState, int backgroundCallState);
     void notifyDisconnectCause(int phoneId, int subId, int disconnectCause,
             int preciseDisconnectCause);
-    void notifyPreciseDataConnectionFailed(int phoneId, int subId, String apnType, String apn,
+    void notifyPreciseDataConnectionFailed(int phoneId, int subId, int apnType, String apn,
             int failCause);
     void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo);
     void notifySrvccStateChanged(in int subId, in int lteState);
diff --git a/core/java/com/android/internal/util/GrowingArrayUtils.java b/core/java/com/android/internal/util/GrowingArrayUtils.java
index 9f56366..597fe6b 100644
--- a/core/java/com/android/internal/util/GrowingArrayUtils.java
+++ b/core/java/com/android/internal/util/GrowingArrayUtils.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.util;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * A helper class that aims to provide comparable growth performance to ArrayList, but on primitive
diff --git a/core/java/com/android/internal/util/HexDump.java b/core/java/com/android/internal/util/HexDump.java
index 6ffc928..ad88dd6 100644
--- a/core/java/com/android/internal/util/HexDump.java
+++ b/core/java/com/android/internal/util/HexDump.java
@@ -17,7 +17,7 @@
 package com.android.internal.util;
 
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 public class HexDump
 {
diff --git a/core/java/com/android/internal/util/IState.java b/core/java/com/android/internal/util/IState.java
index eb66e2c..07837bf 100644
--- a/core/java/com/android/internal/util/IState.java
+++ b/core/java/com/android/internal/util/IState.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.util;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Message;
 
 /**
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
index 03a555e..34c6a05 100644
--- a/core/java/com/android/internal/util/IndentingPrintWriter.java
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -16,7 +16,8 @@
 
 package com.android.internal.util;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import java.io.PrintWriter;
 import java.io.Writer;
 import java.util.Arrays;
diff --git a/core/java/com/android/internal/util/JournaledFile.java b/core/java/com/android/internal/util/JournaledFile.java
index 065cc5b2..a9d8f72 100644
--- a/core/java/com/android/internal/util/JournaledFile.java
+++ b/core/java/com/android/internal/util/JournaledFile.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.util;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 
 import java.io.File;
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 67cfc3a..27c2478 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -121,11 +121,7 @@
     }
 
     public static boolean isEnabled(Context ctx) {
-        return getInstance(ctx).isEnabled();
-    }
-
-    public boolean isEnabled() {
-        return Build.IS_DEBUGGABLE && mEnabled;
+        return Build.IS_DEBUGGABLE && getInstance(ctx).mEnabled;
     }
 
     /**
diff --git a/core/java/com/android/internal/util/MemInfoReader.java b/core/java/com/android/internal/util/MemInfoReader.java
index 580c2fa..5de77d9 100644
--- a/core/java/com/android/internal/util/MemInfoReader.java
+++ b/core/java/com/android/internal/util/MemInfoReader.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.util;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Debug;
 import android.os.StrictMode;
 
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 3fff5c2..5bc96d8 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -18,7 +18,7 @@
 
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.text.TextUtils;
 
 import java.util.Collection;
diff --git a/core/java/com/android/internal/util/State.java b/core/java/com/android/internal/util/State.java
index 3c61e03..636378e 100644
--- a/core/java/com/android/internal/util/State.java
+++ b/core/java/com/android/internal/util/State.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.util;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Message;
 
 /**
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index 6c217e5..0c24065 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.util;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index 8799e3d..c1be33a 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -16,10 +16,10 @@
 
 package com.android.internal.util;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.text.TextUtils;
 import android.util.ArrayMap;
diff --git a/core/java/com/android/internal/view/ActionBarPolicy.java b/core/java/com/android/internal/view/ActionBarPolicy.java
index d18c35e..d16cb43 100644
--- a/core/java/com/android/internal/view/ActionBarPolicy.java
+++ b/core/java/com/android/internal/view/ActionBarPolicy.java
@@ -16,15 +16,15 @@
 
 package com.android.internal.view;
 
-import com.android.internal.R;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Build;
 
+import com.android.internal.R;
+
 /**
  * Allows components to query for various configuration policy decisions
  * about how the action bar should lay out and behave on the current device.
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 2ac2975..5dd3389b 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.view;
 
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.input.InputManager;
@@ -34,8 +35,6 @@
 
 import com.android.internal.os.IResultReceiver;
 
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
 import java.io.IOException;
 
 public class BaseIWindow extends IWindow.Stub {
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index ececba1..6278d4a3 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java
index 1b133d2..a5964b5 100644
--- a/core/java/com/android/internal/view/InputBindResult.java
+++ b/core/java/com/android/internal/view/InputBindResult.java
@@ -20,7 +20,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.ServiceConnection;
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 0c057ea..a41048c 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -19,7 +19,7 @@
 import android.annotation.AnyThread;
 import android.annotation.BinderThread;
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.inputmethodservice.AbstractInputMethodService;
 import android.os.Bundle;
 import android.os.Handler;
diff --git a/core/java/com/android/internal/view/WindowManagerPolicyThread.java b/core/java/com/android/internal/view/WindowManagerPolicyThread.java
index b009a2d..6d691fc 100644
--- a/core/java/com/android/internal/view/WindowManagerPolicyThread.java
+++ b/core/java/com/android/internal/view/WindowManagerPolicyThread.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.view;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Looper;
 
 /**
diff --git a/core/java/com/android/internal/view/menu/ActionMenu.java b/core/java/com/android/internal/view/menu/ActionMenu.java
index 977c1f6..6482629 100644
--- a/core/java/com/android/internal/view/menu/ActionMenu.java
+++ b/core/java/com/android/internal/view/menu/ActionMenu.java
@@ -16,10 +16,7 @@
 
 package com.android.internal.view.menu;
 
-import java.util.ArrayList;
-import java.util.List;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -30,6 +27,9 @@
 import android.view.MenuItem;
 import android.view.SubMenu;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * @hide
  */
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java
index ed253d5..bd8bcb9 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItem.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -17,7 +17,7 @@
 package com.android.internal.view.menu;
 
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.ColorStateList;
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index eb94db3..7622b93 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.view.menu;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
diff --git a/core/java/com/android/internal/view/menu/ContextMenuBuilder.java b/core/java/com/android/internal/view/menu/ContextMenuBuilder.java
index 3d3aceb..a9f5e47 100644
--- a/core/java/com/android/internal/view/menu/ContextMenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/ContextMenuBuilder.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.view.menu;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.os.IBinder;
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index 3d888d3..539c71e 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -16,13 +16,12 @@
 
 package com.android.internal.view.menu;
 
-import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.text.Layout;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.Gravity;
@@ -30,7 +29,8 @@
 import android.view.View;
 import android.view.ViewDebug;
 import android.widget.TextView;
-import android.text.Layout;
+
+import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
 
 /**
  * The item view for each item in the {@link IconMenuView}.
diff --git a/core/java/com/android/internal/view/menu/IconMenuView.java b/core/java/com/android/internal/view/menu/IconMenuView.java
index 6f26434..9e240db 100644
--- a/core/java/com/android/internal/view/menu/IconMenuView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuView.java
@@ -16,9 +16,7 @@
 
 package com.android.internal.view.menu;
 
-import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -29,10 +27,12 @@
 import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
-import android.view.LayoutInflater;
+
+import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
 
 import java.util.ArrayList;
 
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 0e07ca7..b31ae38 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -18,7 +18,7 @@
 
 
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
diff --git a/core/java/com/android/internal/view/menu/MenuDialogHelper.java b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
index 88d0a03..d02b8f6 100644
--- a/core/java/com/android/internal/view/menu/MenuDialogHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
@@ -16,9 +16,9 @@
 
 package com.android.internal.view.menu;
 
-import android.annotation.UnsupportedAppUsage;
 import android.app.AlertDialog;
 import android.app.Dialog;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.DialogInterface;
 import android.os.IBinder;
 import android.view.KeyEvent;
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 994a9c1..218f518 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -16,10 +16,8 @@
 
 package com.android.internal.view.menu;
 
-import com.android.internal.view.menu.MenuView.ItemView;
-
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
@@ -39,6 +37,8 @@
 import android.view.ViewDebug;
 import android.widget.LinearLayout;
 
+import com.android.internal.view.menu.MenuView.ItemView;
+
 /**
  * @hide
  */
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index d00108e..bac6025 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -20,7 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StyleRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java
index c5df8ad..35b8fef 100644
--- a/core/java/com/android/internal/view/menu/MenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/MenuPresenter.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.os.Parcelable;
 import android.view.ViewGroup;
diff --git a/core/java/com/android/internal/view/menu/MenuView.java b/core/java/com/android/internal/view/menu/MenuView.java
index 67a5530..a31c820 100644
--- a/core/java/com/android/internal/view/menu/MenuView.java
+++ b/core/java/com/android/internal/view/menu/MenuView.java
@@ -16,10 +16,7 @@
 
 package com.android.internal.view.menu;
 
-import com.android.internal.view.menu.MenuBuilder;
-import com.android.internal.view.menu.MenuItemImpl;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.drawable.Drawable;
 
 /**
diff --git a/core/java/com/android/internal/view/menu/SubMenuBuilder.java b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
index cf6d974..6eb215e 100644
--- a/core/java/com/android/internal/view/menu/SubMenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.view.menu;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.view.Menu;
diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java
index 9ccee7f..0f0c1a3 100644
--- a/core/java/com/android/internal/widget/AbsActionBarView.java
+++ b/core/java/com/android/internal/widget/AbsActionBarView.java
@@ -15,26 +15,25 @@
  */
 package com.android.internal.widget;
 
-import com.android.internal.R;
-
-import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
-import android.view.MotionEvent;
-import android.widget.ActionMenuPresenter;
-import android.widget.ActionMenuView;
-
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.DecelerateInterpolator;
+import android.widget.ActionMenuPresenter;
+import android.widget.ActionMenuView;
+
+import com.android.internal.R;
 
 public abstract class AbsActionBarView extends ViewGroup {
     private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator();
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 78ed53f..051526e 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -15,13 +15,7 @@
  */
 package com.android.internal.widget;
 
-import com.android.internal.R;
-
-import android.widget.ActionMenuPresenter;
-import android.widget.ActionMenuView;
-import com.android.internal.view.menu.MenuBuilder;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
@@ -32,9 +26,14 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
+import android.widget.ActionMenuPresenter;
+import android.widget.ActionMenuView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.internal.R;
+import com.android.internal.view.menu.MenuBuilder;
+
 /**
  * @hide
  */
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index e9e3cda..aca0b71 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -18,7 +18,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -41,6 +41,7 @@
 import android.view.WindowInsets;
 import android.widget.OverScroller;
 import android.widget.Toolbar;
+
 import com.android.internal.view.menu.MenuPresenter;
 
 /**
diff --git a/core/java/com/android/internal/widget/AlertDialogLayout.java b/core/java/com/android/internal/widget/AlertDialogLayout.java
index 7a01749..d879b6d 100644
--- a/core/java/com/android/internal/widget/AlertDialogLayout.java
+++ b/core/java/com/android/internal/widget/AlertDialogLayout.java
@@ -18,8 +18,8 @@
 
 import android.annotation.AttrRes;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
 import android.annotation.StyleRes;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java
index 0ca6743..ff13107 100644
--- a/core/java/com/android/internal/widget/ButtonBarLayout.java
+++ b/core/java/com/android/internal/widget/ButtonBarLayout.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.widget;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java
index 35bff6d..74ad815 100644
--- a/core/java/com/android/internal/widget/CachingIconView.java
+++ b/core/java/com/android/internal/widget/CachingIconView.java
@@ -18,7 +18,7 @@
 
 import android.annotation.DrawableRes;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
diff --git a/core/java/com/android/internal/widget/DialogTitle.java b/core/java/com/android/internal/widget/DialogTitle.java
index 405436c..0bfd684 100644
--- a/core/java/com/android/internal/widget/DialogTitle.java
+++ b/core/java/com/android/internal/widget/DialogTitle.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.widget;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.text.Layout;
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 2b648e9..ff3543c8 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.widget;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Bundle;
 import android.text.Editable;
 import android.text.method.KeyListener;
diff --git a/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java b/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java
index cc7911d..9ef9f69 100644
--- a/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java
+++ b/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java
@@ -16,12 +16,12 @@
 
 package com.android.internal.widget;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
-import android.view.View;
 import android.view.MotionEvent;
+import android.view.View;
 import android.widget.LinearLayout;
 
 
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 4f4c8c3..f37a468 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -25,11 +25,11 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.PasswordMetrics;
 import android.app.trust.IStrongAuthTracker;
 import android.app.trust.TrustManager;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -55,10 +55,10 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 
-import com.google.android.collect.Lists;
-
 import libcore.util.HexEncoding;
 
+import com.google.android.collect.Lists;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.security.MessageDigest;
@@ -1599,6 +1599,11 @@
         public static final int STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN = 0x20;
 
         /**
+         * Strong authentication is required to prepare for unattended upgrade.
+         */
+        public static final int STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE = 0x40;
+
+        /**
          * Strong auth flags that do not prevent biometric methods from being accepted as auth.
          * If any other flags are set, biometric authentication is disabled.
          */
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 74a0aa3..4ddc782 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -19,7 +19,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index dd05576..90a18ef 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -77,4 +77,34 @@
      * @return the user password metrics.
      */
     public abstract @Nullable PasswordMetrics getUserPasswordMetrics(int userHandle);
+
+    /**
+     * Prepare for reboot escrow. This triggers the strong auth to be required. After the escrow
+     * is complete as indicated by calling to the listener registered with {@link
+     * #setRebootEscrowListener}, then {@link #armRebootEscrow()} should be called before
+     * rebooting to apply the update.
+     */
+    public abstract void prepareRebootEscrow();
+
+    /**
+     * Registers a listener for when the RebootEscrow HAL has stored its data needed for rebooting
+     * for an OTA.
+     *
+     * @see RebootEscrowListener
+     * @param listener
+     */
+    public abstract void setRebootEscrowListener(RebootEscrowListener listener);
+
+    /**
+     * Requests that any data needed for rebooting is cleared from the RebootEscrow HAL.
+     */
+    public abstract void clearRebootEscrow();
+
+    /**
+     * Should be called immediately before rebooting for an update. This depends on {@link
+     * #prepareRebootEscrow()} having been called and the escrow completing.
+     *
+     * @return true if the arming worked
+     */
+    public abstract boolean armRebootEscrow();
 }
diff --git a/core/java/com/android/internal/widget/NumericTextView.java b/core/java/com/android/internal/widget/NumericTextView.java
index d215670..c8f9011 100644
--- a/core/java/com/android/internal/widget/NumericTextView.java
+++ b/core/java/com/android/internal/widget/NumericTextView.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.widget;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 37046af..dc8d57a 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.widget;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Paint;
diff --git a/core/java/com/android/internal/widget/PreferenceImageView.java b/core/java/com/android/internal/widget/PreferenceImageView.java
index 02a0b8d..43b6b5a 100644
--- a/core/java/com/android/internal/widget/PreferenceImageView.java
+++ b/core/java/com/android/internal/widget/PreferenceImageView.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.widget;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.widget.ImageView;
diff --git a/core/java/com/android/internal/widget/RebootEscrowListener.java b/core/java/com/android/internal/widget/RebootEscrowListener.java
new file mode 100644
index 0000000..1654532
--- /dev/null
+++ b/core/java/com/android/internal/widget/RebootEscrowListener.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+/**
+ * Private API to be notified about reboot escrow events.
+ *
+ * {@hide}
+ */
+public interface RebootEscrowListener {
+    /**
+     * Called when the preparation status has changed. When {@code prepared} is {@code true} the
+     * user has entered their lock screen knowledge factor (LSKF) and the HAL has confirmed that
+     * it is ready to retrieve the secret after a reboot. When {@code prepared} is {@code false}
+     * then those conditions are not true.
+     */
+    void onPreparedForReboot(boolean prepared);
+}
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index b66a7b4..43a227a 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -20,7 +20,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.database.Observable;
diff --git a/core/java/com/android/internal/widget/ScrollBarUtils.java b/core/java/com/android/internal/widget/ScrollBarUtils.java
index 982e315..3e9d697 100644
--- a/core/java/com/android/internal/widget/ScrollBarUtils.java
+++ b/core/java/com/android/internal/widget/ScrollBarUtils.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.widget;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 public class ScrollBarUtils {
 
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index 5d48ab9..aa0b0bb 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -15,13 +15,11 @@
  */
 package com.android.internal.widget;
 
-import com.android.internal.view.ActionBarPolicy;
-
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.annotation.UnsupportedAppUsage;
 import android.app.ActionBar;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.drawable.Drawable;
@@ -42,6 +40,8 @@
 import android.widget.Spinner;
 import android.widget.TextView;
 
+import com.android.internal.view.ActionBarPolicy;
+
 /**
  * This widget implements the dynamic action bar tab behavior that can change
  * across different configurations or circumstances.
diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java
index 4b5d624..5e6f3a4 100644
--- a/core/java/com/android/internal/widget/SlidingTab.java
+++ b/core/java/com/android/internal/widget/SlidingTab.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.widget;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -34,12 +34,12 @@
 import android.view.ViewGroup;
 import android.view.animation.AlphaAnimation;
 import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
 import android.view.animation.LinearInterpolator;
 import android.view.animation.TranslateAnimation;
-import android.view.animation.Animation.AnimationListener;
 import android.widget.ImageView;
-import android.widget.TextView;
 import android.widget.ImageView.ScaleType;
+import android.widget.TextView;
 
 import com.android.internal.R;
 
diff --git a/core/java/com/android/internal/widget/TextViewInputDisabler.java b/core/java/com/android/internal/widget/TextViewInputDisabler.java
index 8d8f0fe..57806eb 100644
--- a/core/java/com/android/internal/widget/TextViewInputDisabler.java
+++ b/core/java/com/android/internal/widget/TextViewInputDisabler.java
@@ -16,7 +16,7 @@
 
 package com.android.internal.widget;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.text.InputFilter;
 import android.text.Spanned;
 import android.widget.TextView;
diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java
index 7d36b02..c8a86d1 100644
--- a/core/java/com/android/internal/widget/ViewPager.java
+++ b/core/java/com/android/internal/widget/ViewPager.java
@@ -18,7 +18,7 @@
 
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
diff --git a/core/java/com/android/server/ResettableTimeout.java b/core/java/com/android/server/ResettableTimeout.java
index 64083f7..511af94 100644
--- a/core/java/com/android/server/ResettableTimeout.java
+++ b/core/java/com/android/server/ResettableTimeout.java
@@ -16,10 +16,9 @@
 
 package com.android.server;
 
-import android.os.SystemClock;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.ConditionVariable;
+import android.os.SystemClock;
 
 /**
  * Utility class that you can call on with a timeout, and get called back
diff --git a/core/java/com/android/server/net/BaseNetworkObserver.java b/core/java/com/android/server/net/BaseNetworkObserver.java
index e1a10a5..2a9c0b4 100644
--- a/core/java/com/android/server/net/BaseNetworkObserver.java
+++ b/core/java/com/android/server/net/BaseNetworkObserver.java
@@ -16,7 +16,7 @@
 
 package com.android.server.net;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.net.INetworkManagementEventObserver;
 import android.net.LinkAddress;
 import android.net.RouteInfo;
diff --git a/core/java/com/android/server/net/NetlinkTracker.java b/core/java/com/android/server/net/NetlinkTracker.java
index 647fb5b..b57397f 100644
--- a/core/java/com/android/server/net/NetlinkTracker.java
+++ b/core/java/com/android/server/net/NetlinkTracker.java
@@ -16,7 +16,7 @@
 
 package com.android.server.net;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.RouteInfo;
diff --git a/core/java/com/google/android/collect/Lists.java b/core/java/com/google/android/collect/Lists.java
index 8f6594a..585847d 100644
--- a/core/java/com/google/android/collect/Lists.java
+++ b/core/java/com/google/android/collect/Lists.java
@@ -16,7 +16,8 @@
 
 package com.google.android.collect;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import java.util.ArrayList;
 import java.util.Collections;
 
diff --git a/core/java/com/google/android/collect/Maps.java b/core/java/com/google/android/collect/Maps.java
index 6ba3320..cd4c128 100644
--- a/core/java/com/google/android/collect/Maps.java
+++ b/core/java/com/google/android/collect/Maps.java
@@ -16,7 +16,7 @@
 
 package com.google.android.collect;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.util.ArrayMap;
 
 import java.util.HashMap;
diff --git a/core/java/com/google/android/collect/Sets.java b/core/java/com/google/android/collect/Sets.java
index 09b5e51..c67a88a 100644
--- a/core/java/com/google/android/collect/Sets.java
+++ b/core/java/com/google/android/collect/Sets.java
@@ -16,7 +16,7 @@
 
 package com.google.android.collect;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.util.ArraySet;
 
 import java.util.Collections;
diff --git a/core/java/com/google/android/util/AbstractMessageParser.java b/core/java/com/google/android/util/AbstractMessageParser.java
index f11e6b2..0da7607 100644
--- a/core/java/com/google/android/util/AbstractMessageParser.java
+++ b/core/java/com/google/android/util/AbstractMessageParser.java
@@ -16,7 +16,7 @@
 
 package com.google.android.util;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import java.util.ArrayList;
 import java.util.HashMap;
diff --git a/core/java/org/apache/http/conn/ssl/AbstractVerifier.java b/core/java/org/apache/http/conn/ssl/AbstractVerifier.java
index 36d6e22..2848ad7 100644
--- a/core/java/org/apache/http/conn/ssl/AbstractVerifier.java
+++ b/core/java/org/apache/http/conn/ssl/AbstractVerifier.java
@@ -31,7 +31,7 @@
 
 package org.apache.http.conn.ssl;
 
-import java.util.regex.Pattern;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import java.io.IOException;
 import java.security.cert.Certificate;
@@ -43,10 +43,10 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
-import java.util.logging.Logger;
 import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
 
-import android.annotation.UnsupportedAppUsage;
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSocket;
diff --git a/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java b/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java
index b2e8b5e..ffae757 100644
--- a/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java
+++ b/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java
@@ -31,20 +31,14 @@
 
 package org.apache.http.conn.ssl;
 
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+
 import org.apache.http.conn.scheme.HostNameResolver;
 import org.apache.http.conn.scheme.LayeredSocketFactory;
 import org.apache.http.params.HttpConnectionParams;
 import org.apache.http.params.HttpParams;
 
-import android.annotation.UnsupportedAppUsage;
-import android.os.Build;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
@@ -57,6 +51,14 @@
 import java.security.SecureRandom;
 import java.security.UnrecoverableKeyException;
 
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
 /**
  * Layered socket factory for TLS/SSL connections, based on JSSE.
  *.
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 5624f45..082a289 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -33,27 +33,27 @@
 
 // Static whitelist of open paths that the zygote is allowed to keep open.
 static const char* kPathWhitelist[] = {
-  "/apex/com.android.appsearch/javalib/framework-appsearch.jar",
-  "/apex/com.android.conscrypt/javalib/conscrypt.jar",
-  "/apex/com.android.ipsec/javalib/ike.jar",
-  "/apex/com.android.media/javalib/updatable-media.jar",
-  "/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar",
-  "/apex/com.android.os.statsd/javalib/framework-statsd.jar",
-  "/apex/com.android.sdkext/javalib/framework-sdkext.jar",
-  "/apex/com.android.wifi/javalib/framework-wifi.jar",
-  "/apex/com.android.tethering/javalib/framework-tethering.jar",
-  "/dev/null",
-  "/dev/socket/zygote",
-  "/dev/socket/zygote_secondary",
-  "/dev/socket/usap_pool_primary",
-  "/dev/socket/usap_pool_secondary",
-  "/dev/socket/webview_zygote",
-  "/dev/socket/heapprofd",
-  "/sys/kernel/debug/tracing/trace_marker",
-  "/system/framework/framework-res.apk",
-  "/dev/urandom",
-  "/dev/ion",
-  "/dev/dri/renderD129", // Fixes b/31172436
+        "/apex/com.android.appsearch/javalib/framework-appsearch.jar",
+        "/apex/com.android.conscrypt/javalib/conscrypt.jar",
+        "/apex/com.android.ipsec/javalib/ike.jar",
+        "/apex/com.android.media/javalib/updatable-media.jar",
+        "/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar",
+        "/apex/com.android.os.statsd/javalib/framework-statsd.jar",
+        "/apex/com.android.sdkext/javalib/framework-sdkextensions.jar",
+        "/apex/com.android.wifi/javalib/framework-wifi.jar",
+        "/apex/com.android.tethering/javalib/framework-tethering.jar",
+        "/dev/null",
+        "/dev/socket/zygote",
+        "/dev/socket/zygote_secondary",
+        "/dev/socket/usap_pool_primary",
+        "/dev/socket/usap_pool_secondary",
+        "/dev/socket/webview_zygote",
+        "/dev/socket/heapprofd",
+        "/sys/kernel/debug/tracing/trace_marker",
+        "/system/framework/framework-res.apk",
+        "/dev/urandom",
+        "/dev/ion",
+        "/dev/dri/renderD129", // Fixes b/31172436
 };
 
 static const char kFdPath[] = "/proc/self/fd";
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index d5a3b5e..b83b31c 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -702,6 +702,12 @@
     // CATEGORY: SETTINGS
     // OS: R
     ACTION_DASHBOARD_VISIBLE_TIME = 1729;
+
+    // ACTION: Allow "Access all files" for an app
+    APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_ALLOW = 1730;
+
+    // ACTION: Deny "Access all files" for an app
+    APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_DENY = 1731;
 }
 
 /**
@@ -2542,4 +2548,9 @@
     // OS: R
     FUELGAUGE_BATTERY_SHARE = 1821;
 
+    // OPEN: Settings -> Apps & Notifications -> Special App Access
+    // CATEGORY: SETTINGS
+    // OS: R
+    MANAGE_EXTERNAL_STORAGE = 1822;
+
 }
diff --git a/core/proto/android/server/animationadapter.proto b/core/proto/android/server/animationadapter.proto
index 70627ed..c6925f4 100644
--- a/core/proto/android/server/animationadapter.proto
+++ b/core/proto/android/server/animationadapter.proto
@@ -50,6 +50,7 @@
     optional WindowAnimationSpecProto window = 1;
     optional MoveAnimationSpecProto move = 2;
     optional AlphaAnimationSpecProto alpha = 3;
+    optional RotationAnimationSpecProto rotate = 4;
 }
 
 /* represents WindowAnimationSpec */
@@ -76,3 +77,12 @@
     optional float to = 2;
     optional int64 duration_ms = 3;
 }
+
+/* represents RotationAnimationSpec */
+message RotationAnimationSpecProto {
+    option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional float start_luma = 1;
+    optional float end_luma = 2;
+    optional int64 duration_ms = 3;
+}
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index ee5144c..9054d54 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -153,4 +153,5 @@
   CROSS_PROFILE_APPS_START_ACTIVITY_AS_USER = 126;
   SET_AUTO_TIME = 127;
   SET_AUTO_TIME_ZONE = 128;
+  SET_PACKAGES_PROTECTED = 129;
 }
diff --git a/core/res/res/anim/screen_rotate_0_enter.xml b/core/res/res/anim/screen_rotate_0_enter.xml
index 93cf365..629be7e 100644
--- a/core/res/res/anim/screen_rotate_0_enter.xml
+++ b/core/res/res/anim/screen_rotate_0_enter.xml
@@ -1,25 +1,25 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/*
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
+  ~ Copyright (C) 2019 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.
+  -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:shareInterpolator="false">
-    <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:duration="@android:integer/config_shortAnimTime" />
+    android:shareInterpolator="false">
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:interpolator="@interpolator/screen_rotation_alpha_in"
+        android:fillEnabled="true"
+        android:fillBefore="true" android:fillAfter="true"
+        android:duration="@android:integer/config_screen_rotation_fade_in" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_0_exit.xml b/core/res/res/anim/screen_rotate_0_exit.xml
index 37d5a411..fa046a0 100644
--- a/core/res/res/anim/screen_rotate_0_exit.xml
+++ b/core/res/res/anim/screen_rotate_0_exit.xml
@@ -1,22 +1,25 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-/*
-** Copyright 2010, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
+  ~ Copyright (C) 2019 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.
+  -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:shareInterpolator="false">
+    android:shareInterpolator="false">
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+        android:interpolator="@interpolator/screen_rotation_alpha_out"
+        android:fillEnabled="true"
+        android:fillBefore="true" android:fillAfter="true"
+        android:duration="@android:integer/config_screen_rotation_fade_out" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_0_frame.xml b/core/res/res/anim/screen_rotate_0_frame.xml
deleted file mode 100644
index 5ea9bf8..0000000
--- a/core/res/res/anim/screen_rotate_0_frame.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 2012, 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.
-*/
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:shareInterpolator="false">
-    <alpha android:fromAlpha="1.0" android:toAlpha="1.0"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:duration="@android:integer/config_shortAnimTime" />
-</set>
diff --git a/core/res/res/anim/screen_rotate_180_enter.xml b/core/res/res/anim/screen_rotate_180_enter.xml
index 688a8d5..889a615 100644
--- a/core/res/res/anim/screen_rotate_180_enter.xml
+++ b/core/res/res/anim/screen_rotate_180_enter.xml
@@ -18,11 +18,11 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:shareInterpolator="false">
+    android:shareInterpolator="false">
     <rotate android:fromDegrees="180" android:toDegrees="0"
-            android:pivotX="50%" android:pivotY="50%"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_mediumAnimTime" />
+        android:pivotX="50%" android:pivotY="50%"
+        android:fillEnabled="true"
+        android:fillBefore="true" android:fillAfter="true"
+        android:interpolator="@interpolator/fast_out_slow_in"
+        android:duration="@android:integer/config_screen_rotation_total_180" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_180_exit.xml b/core/res/res/anim/screen_rotate_180_exit.xml
index 58a1868..766fcfa 100644
--- a/core/res/res/anim/screen_rotate_180_exit.xml
+++ b/core/res/res/anim/screen_rotate_180_exit.xml
@@ -18,11 +18,11 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:shareInterpolator="false">
+    android:shareInterpolator="false">
     <rotate android:fromDegrees="0" android:toDegrees="-180"
-            android:pivotX="50%" android:pivotY="50%"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_mediumAnimTime" />
+        android:pivotX="50%" android:pivotY="50%"
+        android:fillEnabled="true"
+        android:fillBefore="true" android:fillAfter="true"
+        android:interpolator="@interpolator/fast_out_slow_in"
+        android:duration="@android:integer/config_screen_rotation_total_180" />
 </set>
\ No newline at end of file
diff --git a/core/res/res/anim/screen_rotate_alpha.xml b/core/res/res/anim/screen_rotate_alpha.xml
index c49ef9c..2cac982 100644
--- a/core/res/res/anim/screen_rotate_alpha.xml
+++ b/core/res/res/anim/screen_rotate_alpha.xml
@@ -20,8 +20,8 @@
 <set xmlns:android="http://schemas.android.com/apk/res/android"
         android:shareInterpolator="false">
     <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
-           android:interpolator="@interpolator/decelerate_quint"
+           android:interpolator="@interpolator/screen_rotation_alpha_out"
            android:fillEnabled="true"
            android:fillBefore="true" android:fillAfter="true"
-           android:duration="@android:integer/config_mediumAnimTime" />
+           android:duration="@android:integer/config_screen_rotation_fade_out" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_minus_90_enter.xml b/core/res/res/anim/screen_rotate_minus_90_enter.xml
index b16d5fc..87fd25e 100644
--- a/core/res/res/anim/screen_rotate_minus_90_enter.xml
+++ b/core/res/res/anim/screen_rotate_minus_90_enter.xml
@@ -18,19 +18,17 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:shareInterpolator="false">
-    <!-- Version for two-phase anim
+    android:shareInterpolator="false">
     <rotate android:fromDegrees="-90" android:toDegrees="0"
-            android:pivotX="50%" android:pivotY="50%"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_longAnimTime" />
-    -->
-    <rotate android:fromDegrees="-90" android:toDegrees="0"
-            android:pivotX="50%" android:pivotY="50%"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:duration="@android:integer/config_mediumAnimTime" />
+        android:pivotX="50%" android:pivotY="50%"
+        android:fillEnabled="true"
+        android:fillBefore="true" android:fillAfter="true"
+        android:interpolator="@interpolator/fast_out_slow_in"
+        android:duration="@android:integer/config_screen_rotation_total_90" />
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:fillEnabled="true"
+        android:fillBefore="true" android:fillAfter="true"
+        android:interpolator="@interpolator/screen_rotation_alpha_in"
+        android:startOffset="@android:integer/config_screen_rotation_fade_in_delay"
+        android:duration="@android:integer/config_screen_rotation_fade_in" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_minus_90_exit.xml b/core/res/res/anim/screen_rotate_minus_90_exit.xml
index 0927dd3..c3aee14 100644
--- a/core/res/res/anim/screen_rotate_minus_90_exit.xml
+++ b/core/res/res/anim/screen_rotate_minus_90_exit.xml
@@ -18,26 +18,16 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:shareInterpolator="false">
-    <!-- Version for two-phase animation
+    android:shareInterpolator="false">
     <rotate android:fromDegrees="0" android:toDegrees="90"
-            android:pivotX="50%" android:pivotY="50%"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_longAnimTime" />
-    -->
-    <scale android:fromXScale="100%" android:toXScale="100%p"
-            android:fromYScale="100%" android:toYScale="100%p"
-            android:pivotX="50%" android:pivotY="50%"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_mediumAnimTime" />
-    <rotate android:fromDegrees="0" android:toDegrees="90"
-            android:pivotX="50%" android:pivotY="50%"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_mediumAnimTime" />
+        android:pivotX="50%" android:pivotY="50%"
+        android:fillEnabled="true"
+        android:fillBefore="true" android:fillAfter="true"
+        android:interpolator="@interpolator/fast_out_slow_in"
+        android:duration="@android:integer/config_screen_rotation_total_90" />
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+        android:fillEnabled="true"
+        android:fillBefore="true" android:fillAfter="true"
+        android:interpolator="@interpolator/screen_rotation_alpha_out"
+        android:duration="@android:integer/config_screen_rotation_fade_out" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_plus_90_enter.xml b/core/res/res/anim/screen_rotate_plus_90_enter.xml
index 86a8d24..8849db4 100644
--- a/core/res/res/anim/screen_rotate_plus_90_enter.xml
+++ b/core/res/res/anim/screen_rotate_plus_90_enter.xml
@@ -18,19 +18,16 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:shareInterpolator="false">
-    <!-- Version for two-phase animation
+    android:shareInterpolator="false">
     <rotate android:fromDegrees="90" android:toDegrees="0"
-            android:pivotX="50%" android:pivotY="50%"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_longAnimTime" />
-    -->
-    <rotate android:fromDegrees="90" android:toDegrees="0"
-            android:pivotX="50%" android:pivotY="50%"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_mediumAnimTime" />
+        android:pivotX="50%" android:pivotY="50%"
+        android:fillEnabled="true"
+        android:fillBefore="true" android:fillAfter="true"
+        android:interpolator="@interpolator/fast_out_slow_in"
+        android:duration="@android:integer/config_screen_rotation_total_90" />
+    <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:fillEnabled="true"
+        android:interpolator="@interpolator/screen_rotation_alpha_in"
+        android:startOffset="@android:integer/config_screen_rotation_fade_in_delay"
+        android:duration="@android:integer/config_screen_rotation_fade_in" />
 </set>
diff --git a/core/res/res/anim/screen_rotate_plus_90_exit.xml b/core/res/res/anim/screen_rotate_plus_90_exit.xml
index fd786f9..de84c3b 100644
--- a/core/res/res/anim/screen_rotate_plus_90_exit.xml
+++ b/core/res/res/anim/screen_rotate_plus_90_exit.xml
@@ -18,26 +18,16 @@
 -->
 
 <set xmlns:android="http://schemas.android.com/apk/res/android"
-        android:shareInterpolator="false">
-    <!-- Version for two-phase animation
+    android:shareInterpolator="false">
     <rotate android:fromDegrees="0" android:toDegrees="-90"
-            android:pivotX="50%" android:pivotY="50%"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_longAnimTime" />
-    -->
-    <scale android:fromXScale="100%" android:toXScale="100%p"
-            android:fromYScale="100%" android:toYScale="100%p"
-            android:pivotX="50%" android:pivotY="50%"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_mediumAnimTime" />
-    <rotate android:fromDegrees="0" android:toDegrees="-90"
-            android:pivotX="50%" android:pivotY="50%"
-            android:interpolator="@interpolator/decelerate_quint"
-            android:fillEnabled="true"
-            android:fillBefore="true" android:fillAfter="true"
-            android:duration="@android:integer/config_mediumAnimTime" />
+        android:pivotX="50%" android:pivotY="50%"
+        android:fillEnabled="true"
+        android:fillBefore="true" android:fillAfter="true"
+        android:interpolator="@interpolator/fast_out_slow_in"
+        android:duration="@android:integer/config_screen_rotation_total_90" />
+    <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+        android:interpolator="@interpolator/screen_rotation_alpha_out"
+        android:fillEnabled="true"
+        android:fillBefore="true" android:fillAfter="true"
+        android:duration="@android:integer/config_screen_rotation_fade_out" />
 </set>
diff --git a/core/res/res/interpolator/screen_rotation_alpha_in.xml b/core/res/res/interpolator/screen_rotation_alpha_in.xml
new file mode 100644
index 0000000..9c566a7
--- /dev/null
+++ b/core/res/res/interpolator/screen_rotation_alpha_in.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 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.
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.15"
+    android:controlY1="0.45"
+    android:controlX2="0.33"
+    android:controlY2="1"/>
diff --git a/core/res/res/interpolator/screen_rotation_alpha_out.xml b/core/res/res/interpolator/screen_rotation_alpha_out.xml
new file mode 100644
index 0000000..73a37d4
--- /dev/null
+++ b/core/res/res/interpolator/screen_rotation_alpha_out.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 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.
+  -->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.57"
+    android:controlY1="0"
+    android:controlX2="0.71"
+    android:controlY2=".43"/>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e839709..245aed1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -147,6 +147,24 @@
     <integer name="config_activityShortDur">150</integer>
     <integer name="config_activityDefaultDur">220</integer>
 
+    <!-- Fade out time for screen rotation -->
+    <integer name="config_screen_rotation_fade_out">116</integer>
+
+    <!-- Fade in time for screen rotation -->
+    <integer name="config_screen_rotation_fade_in">233</integer>
+
+    <!-- Fade in delay time for screen rotation -->
+    <integer name="config_screen_rotation_fade_in_delay">100</integer>
+
+    <!-- Total time for 90 degree screen rotation animations -->
+    <integer name="config_screen_rotation_total_90">333</integer>
+
+    <!-- Total time for 180 degree screen rotation animation -->
+    <integer name="config_screen_rotation_total_180">433</integer>
+
+    <!-- Total time for the rotation background color transition -->
+    <integer name="config_screen_rotation_color_transition">200</integer>
+
     <!-- The duration (in milliseconds) of the tooltip show/hide animations. -->
     <integer name="config_tooltipAnimTime">150</integer>
 
@@ -4284,4 +4302,12 @@
 
     <!-- Whether or not to use assistant stream volume separately from music volume -->
     <bool name="config_useAssistantVolume">false</bool>
+
+    <!-- Whether to use a custom Bugreport handling. When true, ACTION_CUSTOM_BUGREPORT_REQUESTED
+         intent is broadcasted on bugreporting chord (instead of the default full bugreport
+         generation). -->
+    <bool name="config_customBugreport">false</bool>
+
+    <!-- Class name of the custom country detector to be used. -->
+    <string name="config_customCountryDetector" translatable="false">com.android.server.location.ComprehensiveCountryDetector</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 129c862..cdbf14b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1805,7 +1805,6 @@
   <!-- From services -->
   <java-symbol type="anim" name="screen_rotate_0_enter" />
   <java-symbol type="anim" name="screen_rotate_0_exit" />
-  <java-symbol type="anim" name="screen_rotate_0_frame" />
   <java-symbol type="anim" name="screen_rotate_180_enter" />
   <java-symbol type="anim" name="screen_rotate_180_exit" />
   <java-symbol type="anim" name="screen_rotate_180_frame" />
@@ -1981,6 +1980,7 @@
   <java-symbol type="integer" name="config_virtualKeyQuietTimeMillis" />
   <java-symbol type="integer" name="config_brightness_ramp_rate_fast" />
   <java-symbol type="integer" name="config_brightness_ramp_rate_slow" />
+  <java-symbol type="integer" name="config_screen_rotation_color_transition" />
   <java-symbol type="layout" name="am_compat_mode_dialog" />
   <java-symbol type="layout" name="launch_warning" />
   <java-symbol type="layout" name="safe_mode" />
@@ -3605,6 +3605,8 @@
 
   <java-symbol type="bool" name="config_maskMainBuiltInDisplayCutout" />
 
+  <java-symbol type="string" name="config_customCountryDetector" />
+
   <!-- For Foldables -->
   <java-symbol type="bool" name="config_lidControlsDisplayFold" />
   <java-symbol type="string" name="config_foldedArea" />
diff --git a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
index de6f8f7..750ffa1 100644
--- a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
@@ -22,7 +22,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import org.junit.Test;
 
diff --git a/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java
index 9b3d0c9..b88c36f 100644
--- a/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java
@@ -22,7 +22,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import org.junit.Test;
 
diff --git a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
index bee270e..ba29a97 100644
--- a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
@@ -22,7 +22,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import org.junit.Test;
 
diff --git a/core/tests/coretests/src/android/os/ExternalVibrationTest.java b/core/tests/coretests/src/android/os/ExternalVibrationTest.java
new file mode 100644
index 0000000..3b872d5
--- /dev/null
+++ b/core/tests/coretests/src/android/os/ExternalVibrationTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.mock;
+
+import android.media.AudioAttributes;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ExternalVibrationTest {
+    @Test
+    public void testSerialization() {
+        AudioAttributes audio = new AudioAttributes.Builder().build();
+        IExternalVibrationController controller = mock(IExternalVibrationController.class);
+        ExternalVibration original = new ExternalVibration(
+                123, // uid
+                "pkg",
+                audio,
+                controller);
+        Parcel p = Parcel.obtain();
+        original.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        ExternalVibration restored = ExternalVibration.CREATOR.createFromParcel(p);
+        assertEquals(original, restored);
+    }
+}
+
diff --git a/core/tests/coretests/src/android/util/TimestampedValueTest.java b/core/tests/coretests/src/android/os/TimestampedValueTest.java
similarity index 98%
rename from core/tests/coretests/src/android/util/TimestampedValueTest.java
rename to core/tests/coretests/src/android/os/TimestampedValueTest.java
index 6fc2400..f36d9e6 100644
--- a/core/tests/coretests/src/android/util/TimestampedValueTest.java
+++ b/core/tests/coretests/src/android/os/TimestampedValueTest.java
@@ -14,14 +14,12 @@
  * limitations under the License.
  */
 
-package android.util;
+package android.os;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
 
-import android.os.Parcel;
-
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Test;
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index 25ac400..f497db2 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -27,9 +27,12 @@
 
 import static org.hamcrest.Matchers.emptyString;
 import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
 import android.app.Instrumentation;
+import android.view.MotionEvent;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -221,4 +224,101 @@
         onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("ccc"), text.indexOf("ddd")));
         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(initialCursorPosition));
     }
+
+    @Test
+    public void testEditor_onTouchEvent_cursorDrag() throws Throwable {
+        String text = "testEditor_onTouchEvent_cursorDrag";
+        onView(withId(R.id.textview)).perform(replaceText(text));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+        TextView tv = mActivity.findViewById(R.id.textview);
+        Editor editor = tv.getEditorForTesting();
+
+        // Simulate a tap-and-drag gesture. This should trigger a cursor drag.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
+        mInstrumentation.waitForIdleSync();
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event2Time = 1002;
+        MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
+        mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
+        mInstrumentation.waitForIdleSync();
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event3Time = 1003;
+        MotionEvent event3 = moveEvent(event3Time, event3Time, 120f, 30f);
+        mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
+        mInstrumentation.waitForIdleSync();
+        assertTrue(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event4Time = 1004;
+        MotionEvent event4 = upEvent(event3Time, event4Time, 120f, 30f);
+        mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
+        mInstrumentation.waitForIdleSync();
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+    }
+
+    @Test
+    public void testEditor_onTouchEvent_selectionDrag() throws Throwable {
+        String text = "testEditor_onTouchEvent_selectionDrag";
+        onView(withId(R.id.textview)).perform(replaceText(text));
+        onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+        TextView tv = mActivity.findViewById(R.id.textview);
+        Editor editor = tv.getEditorForTesting();
+
+        // Simulate a double-tap followed by a drag. This should trigger a selection drag.
+        long event1Time = 1001;
+        MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+        mActivity.runOnUiThread(() -> editor.onTouchEvent(event1));
+        mInstrumentation.waitForIdleSync();
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event2Time = 1002;
+        MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+        mActivity.runOnUiThread(() -> editor.onTouchEvent(event2));
+        mInstrumentation.waitForIdleSync();
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+        long event3Time = 1003;
+        MotionEvent event3 = downEvent(event3Time, event3Time, 20f, 30f);
+        mActivity.runOnUiThread(() -> editor.onTouchEvent(event3));
+        mInstrumentation.waitForIdleSync();
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertTrue(editor.getSelectionController().isCursorBeingModified());
+
+        long event4Time = 1004;
+        MotionEvent event4 = moveEvent(event3Time, event4Time, 120f, 30f);
+        mActivity.runOnUiThread(() -> editor.onTouchEvent(event4));
+        mInstrumentation.waitForIdleSync();
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertTrue(editor.getSelectionController().isCursorBeingModified());
+
+        long event5Time = 1005;
+        MotionEvent event5 = upEvent(event3Time, event5Time, 120f, 30f);
+        mActivity.runOnUiThread(() -> editor.onTouchEvent(event5));
+        mInstrumentation.waitForIdleSync();
+        assertFalse(editor.getInsertionController().isCursorBeingModified());
+        assertFalse(editor.getSelectionController().isCursorBeingModified());
+    }
+
+    private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
+        return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0);
+    }
+
+    private static MotionEvent upEvent(long downTime, long eventTime, float x, float y) {
+        return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
+    }
+
+    private static MotionEvent moveEvent(long downTime, long eventTime, float x, float y) {
+        return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
+    }
 }
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 6e7f286..bee8d5e 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -21,7 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.Canvas.VertexMode;
 import android.graphics.text.MeasuredText;
 import android.text.GraphicsOperations;
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index d900a42..ac094ba 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -21,8 +21,8 @@
 import android.annotation.ColorLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
 import android.annotation.WorkerThread;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.ResourcesImpl;
 import android.hardware.HardwareBuffer;
 import android.os.Build;
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 5623a8a..bad487b 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -20,7 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.os.Trace;
diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java
index 629d8c1..34eba97 100644
--- a/graphics/java/android/graphics/BitmapRegionDecoder.java
+++ b/graphics/java/android/graphics/BitmapRegionDecoder.java
@@ -15,7 +15,7 @@
 
 package android.graphics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.AssetManager;
 import android.os.Build;
 
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 198d1e7..edf53c4 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -17,7 +17,7 @@
 package android.graphics;
 
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * Shader used to draw a bitmap as a texture. The bitmap can be repeated or
diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java
index cbd4ead..80a3740 100644
--- a/graphics/java/android/graphics/Camera.java
+++ b/graphics/java/android/graphics/Camera.java
@@ -16,7 +16,7 @@
 
 package android.graphics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * A camera instance can be used to compute 3D transformations and
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index a815f20..9a0ca3e 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -22,7 +22,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.text.MeasuredText;
 import android.os.Build;
 
diff --git a/graphics/java/android/graphics/CanvasProperty.java b/graphics/java/android/graphics/CanvasProperty.java
index 1275e08..4263772c 100644
--- a/graphics/java/android/graphics/CanvasProperty.java
+++ b/graphics/java/android/graphics/CanvasProperty.java
@@ -16,7 +16,8 @@
 
 package android.graphics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import com.android.internal.util.VirtualRefBasePtr;
 
 /**
diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java
index 0f7980c..a8b18a9 100644
--- a/graphics/java/android/graphics/ColorMatrixColorFilter.java
+++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * A color filter that transforms colors through a 4x5 color matrix. This filter
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 5ad93f4..ae90995 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -17,7 +17,7 @@
 package android.graphics;
 
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.AssetManager;
 import android.graphics.fonts.FontVariationAxis;
 import android.text.TextUtils;
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 21cc375..c146bbd 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,7 +16,7 @@
 
 package android.graphics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.fonts.FontVariationAxis;
 import android.text.FontConfig;
 import android.util.Xml;
diff --git a/graphics/java/android/graphics/GraphicBuffer.java b/graphics/java/android/graphics/GraphicBuffer.java
index 3b1fc70..99fa5ee 100644
--- a/graphics/java/android/graphics/GraphicBuffer.java
+++ b/graphics/java/android/graphics/GraphicBuffer.java
@@ -16,7 +16,7 @@
 
 package android.graphics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index bcb313e..83432c3 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -28,8 +28,8 @@
 import android.annotation.Nullable;
 import android.annotation.Px;
 import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
 import android.annotation.WorkerThread;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ContentResolver;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.AssetManager;
diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java
index 62a890f..221dfa1 100644
--- a/graphics/java/android/graphics/LightingColorFilter.java
+++ b/graphics/java/android/graphics/LightingColorFilter.java
@@ -22,7 +22,7 @@
 package android.graphics;
 
 import android.annotation.ColorInt;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * A color filter that can be used to simulate simple lighting effects.
diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java
index 12e63c0..3f3ad96 100644
--- a/graphics/java/android/graphics/LinearGradient.java
+++ b/graphics/java/android/graphics/LinearGradient.java
@@ -20,7 +20,7 @@
 import android.annotation.ColorLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 
 public class LinearGradient extends Shader {
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index 22b6401..cf914c2 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -16,7 +16,7 @@
 
 package android.graphics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
diff --git a/graphics/java/android/graphics/Movie.java b/graphics/java/android/graphics/Movie.java
index 6f030ff..4b3924f 100644
--- a/graphics/java/android/graphics/Movie.java
+++ b/graphics/java/android/graphics/Movie.java
@@ -16,7 +16,7 @@
 
 package android.graphics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.AssetManager;
 import android.os.Build;
 
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index c4c1eac..ff32393 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -16,7 +16,7 @@
 
 package android.graphics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * The NinePatch class permits drawing a bitmap in nine or more sections.
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index 1fc056c..91a60c3 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -19,7 +19,7 @@
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.drawable.Drawable;
 
 import java.lang.annotation.Retention;
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 109d863..3b58624 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -24,7 +24,7 @@
 import android.annotation.Nullable;
 import android.annotation.Px;
 import android.annotation.Size;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.fonts.FontVariationAxis;
 import android.os.Build;
 import android.os.LocaleList;
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index 7282d52..1362fd8 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -20,7 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index 8d12cbf..390d3d4 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -17,7 +17,7 @@
 package android.graphics;
 
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import java.io.InputStream;
 import java.io.OutputStream;
diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java
index bc1f66f..1275cb9 100644
--- a/graphics/java/android/graphics/PorterDuff.java
+++ b/graphics/java/android/graphics/PorterDuff.java
@@ -16,7 +16,7 @@
 
 package android.graphics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * <p>This class contains the list of alpha compositing and blending modes
diff --git a/graphics/java/android/graphics/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java
index cc2d3a8..50ecb62 100644
--- a/graphics/java/android/graphics/PorterDuffColorFilter.java
+++ b/graphics/java/android/graphics/PorterDuffColorFilter.java
@@ -18,7 +18,7 @@
 
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * A color filter that can be used to tint the source pixels using a single
diff --git a/graphics/java/android/graphics/RadialGradient.java b/graphics/java/android/graphics/RadialGradient.java
index acbe3da..96b7b9a 100644
--- a/graphics/java/android/graphics/RadialGradient.java
+++ b/graphics/java/android/graphics/RadialGradient.java
@@ -20,7 +20,7 @@
 import android.annotation.ColorLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 public class RadialGradient extends Shader {
     @UnsupportedAppUsage
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 9e1946c..081b851 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -19,7 +19,7 @@
 import android.annotation.CheckResult;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java
index ec7f7a0..d8d9641 100644
--- a/graphics/java/android/graphics/Region.java
+++ b/graphics/java/android/graphics/Region.java
@@ -17,7 +17,7 @@
 package android.graphics;
 
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Pools.SynchronizedPool;
diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java
index 3050d1d..5335aa4 100644
--- a/graphics/java/android/graphics/Shader.java
+++ b/graphics/java/android/graphics/Shader.java
@@ -20,7 +20,7 @@
 import android.annotation.ColorLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 import libcore.util.NativeAllocationRegistry;
 
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 99f440d..697daa8 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -17,7 +17,7 @@
 package android.graphics;
 
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
diff --git a/graphics/java/android/graphics/SweepGradient.java b/graphics/java/android/graphics/SweepGradient.java
index 667f45a..0852004 100644
--- a/graphics/java/android/graphics/SweepGradient.java
+++ b/graphics/java/android/graphics/SweepGradient.java
@@ -20,7 +20,7 @@
 import android.annotation.ColorLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 public class SweepGradient extends Shader {
     @UnsupportedAppUsage
diff --git a/graphics/java/android/graphics/TableMaskFilter.java b/graphics/java/android/graphics/TableMaskFilter.java
index d81c491..204f970 100644
--- a/graphics/java/android/graphics/TableMaskFilter.java
+++ b/graphics/java/android/graphics/TableMaskFilter.java
@@ -16,7 +16,7 @@
 
 package android.graphics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * @hide
diff --git a/graphics/java/android/graphics/TemporaryBuffer.java b/graphics/java/android/graphics/TemporaryBuffer.java
index 0ae2c70..ef3f7f7 100644
--- a/graphics/java/android/graphics/TemporaryBuffer.java
+++ b/graphics/java/android/graphics/TemporaryBuffer.java
@@ -16,7 +16,8 @@
 
 package android.graphics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
 import com.android.internal.util.ArrayUtils;
 
 /**
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 6d20ec3..a2dd9a8 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -25,7 +25,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.AssetManager;
 import android.graphics.fonts.Font;
 import android.graphics.fonts.FontFamily;
diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java
index 6f4adfd..e79fb76 100644
--- a/graphics/java/android/graphics/Xfermode.java
+++ b/graphics/java/android/graphics/Xfermode.java
@@ -21,7 +21,7 @@
 
 package android.graphics;
 
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 
 /**
  * Xfermode is the base class for objects that are called to implement custom
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index 82f5870..d894600 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -19,7 +19,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
index b29fd4d..686f146 100644
--- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
@@ -18,23 +18,23 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.Resources.Theme;
+import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.TypedValue;
-import android.os.SystemClock;
+
+import com.android.internal.R;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
 
-import com.android.internal.R;
-
 /**
  * @hide
  */
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
index 11a46c4..06159d8 100644
--- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -20,7 +20,7 @@
 import android.animation.TimeInterpolator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 66947da..1acf6c5 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -25,9 +25,9 @@
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityThread;
 import android.app.Application;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo.Config;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 57764c2..8c3fa44 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -16,20 +16,20 @@
 
 package android.graphics.drawable;
 
-import com.android.internal.R;
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.os.SystemClock;
+import android.util.AttributeSet;
 
-import java.io.IOException;
+import com.android.internal.R;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.Resources.Theme;
-import android.os.SystemClock;
-import android.util.AttributeSet;
+import java.io.IOException;
 
 /**
  * An object used to create frame-by-frame animations, defined by a series of
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index e4aa774..4e768c9 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -17,7 +17,7 @@
 package android.graphics.drawable;
 
 import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo.Config;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java
index 31fdb02..69ed9b4 100644
--- a/graphics/java/android/graphics/drawable/ClipDrawable.java
+++ b/graphics/java/android/graphics/drawable/ClipDrawable.java
@@ -16,21 +16,23 @@
 
 package android.graphics.drawable;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.Gravity;
+
 import com.android.internal.R;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.Resources.Theme;
-import android.graphics.*;
-import android.view.Gravity;
-import android.util.AttributeSet;
-
 import java.io.IOException;
 
 /**
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index e53f3db..6d4a0c6 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -52,8 +52,8 @@
     DeviceInfo::get()->mMaxTextureSize = maxTextureSize;
 }
 
-void DeviceInfo::onDisplayConfigChanged() {
-    updateDisplayInfo();
+void DeviceInfo::onRefreshRateChanged(int64_t vsyncPeriod) {
+    mVsyncPeriod = vsyncPeriod;
 }
 
 void DeviceInfo::updateDisplayInfo() {
@@ -113,10 +113,11 @@
     ADisplay* primaryDisplay = mDisplays[mPhysicalDisplayIndex];
     status_t status = ADisplay_getCurrentConfig(primaryDisplay, &mCurrentConfig);
     LOG_ALWAYS_FATAL_IF(status, "Failed to get display config, error %d", status);
+
     mWidth = ADisplayConfig_getWidth(mCurrentConfig);
     mHeight = ADisplayConfig_getHeight(mCurrentConfig);
     mDensity = ADisplayConfig_getDensity(mCurrentConfig);
-    mRefreshRate = ADisplayConfig_getFps(mCurrentConfig);
+    mVsyncPeriod = static_cast<int64_t>(1000000000 / ADisplayConfig_getFps(mCurrentConfig));
     mCompositorOffset = ADisplayConfig_getCompositorOffsetNanos(mCurrentConfig);
     mAppOffset = ADisplayConfig_getAppVsyncOffsetNanos(mCurrentConfig);
 }
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index a420746..16a22f4 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -37,7 +37,7 @@
     static int32_t getWidth() { return get()->mWidth; }
     static int32_t getHeight() { return get()->mHeight; }
     static float getDensity() { return get()->mDensity; }
-    static float getRefreshRate() { return get()->mRefreshRate; }
+    static int64_t getVsyncPeriod() { return get()->mVsyncPeriod; }
     static int64_t getCompositorOffset() { return get()->mCompositorOffset; }
     static int64_t getAppOffset() { return get()->mAppOffset; }
 
@@ -47,7 +47,8 @@
     sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; }
     SkColorType getWideColorType() const { return mWideColorType; }
 
-    void onDisplayConfigChanged();
+    // This method should be called whenever the display refresh rate changes.
+    void onRefreshRateChanged(int64_t vsyncPeriod);
 
 private:
     friend class renderthread::RenderThread;
@@ -68,7 +69,7 @@
     int32_t mWidth = 1080;
     int32_t mHeight = 1920;
     float mDensity = 2.0;
-    float mRefreshRate = 60.0;
+    int64_t mVsyncPeriod = 16666666;
     int64_t mCompositorOffset = 0;
     int64_t mAppOffset = 0;
 };
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 10e7160..d25fc4b 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -81,7 +81,7 @@
 
 JankTracker::JankTracker(ProfileDataContainer* globalData) {
     mGlobalData = globalData;
-    nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1_s / DeviceInfo::getRefreshRate());
+    nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
     nsecs_t sfOffset = DeviceInfo::getCompositorOffset();
     nsecs_t offsetDelta = sfOffset - DeviceInfo::getAppOffset();
     // There are two different offset cases. If the offsetDelta is positive
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index a446858..d78f641 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -34,7 +34,6 @@
 #include <GrContextOptions.h>
 #include <gl/GrGLInterface.h>
 
-#include <gui/DisplayEventReceiver.h>
 #include <sys/resource.h>
 #include <utils/Condition.h>
 #include <utils/Log.h>
@@ -45,53 +44,43 @@
 namespace uirenderer {
 namespace renderthread {
 
-// Number of events to read at a time from the DisplayEventReceiver pipe.
-// The value should be large enough that we can quickly drain the pipe
-// using just a few large reads.
-static const size_t EVENT_BUFFER_SIZE = 100;
-
 static bool gHasRenderThreadInstance = false;
 
 static JVMAttachHook gOnStartHook = nullptr;
 
-class DisplayEventReceiverWrapper : public VsyncSource {
+void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) {
+    RenderThread* rt = reinterpret_cast<RenderThread*>(data);
+    rt->mVsyncRequested = false;
+    if (rt->timeLord().vsyncReceived(frameTimeNanos) && !rt->mFrameCallbackTaskPending) {
+        ATRACE_NAME("queue mFrameCallbackTask");
+        rt->mFrameCallbackTaskPending = true;
+        nsecs_t runAt = (frameTimeNanos + rt->mDispatchFrameDelay);
+        rt->queue().postAt(runAt, [=]() { rt->dispatchFrameCallbacks(); });
+    }
+}
+
+void RenderThread::refreshRateCallback(int64_t vsyncPeriod, void* data) {
+    ATRACE_NAME("refreshRateCallback");
+    RenderThread* rt = reinterpret_cast<RenderThread*>(data);
+    DeviceInfo::get()->onRefreshRateChanged(vsyncPeriod);
+    rt->setupFrameInterval();
+}
+
+class ChoreographerSource : public VsyncSource {
 public:
-    DisplayEventReceiverWrapper(std::unique_ptr<DisplayEventReceiver>&& receiver,
-            const std::function<void()>& onDisplayConfigChanged)
-            : mDisplayEventReceiver(std::move(receiver))
-            , mOnDisplayConfigChanged(onDisplayConfigChanged) {}
+    ChoreographerSource(RenderThread* renderThread) : mRenderThread(renderThread) {}
 
     virtual void requestNextVsync() override {
-        status_t status = mDisplayEventReceiver->requestNextVsync();
-        LOG_ALWAYS_FATAL_IF(status != NO_ERROR, "requestNextVsync failed with status: %d", status);
+        AChoreographer_postFrameCallback64(mRenderThread->mChoreographer,
+                                           RenderThread::frameCallback, mRenderThread);
     }
 
-    virtual nsecs_t latestVsyncEvent() override {
-        DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
-        nsecs_t latest = 0;
-        ssize_t n;
-        while ((n = mDisplayEventReceiver->getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
-            for (ssize_t i = 0; i < n; i++) {
-                const DisplayEventReceiver::Event& ev = buf[i];
-                switch (ev.header.type) {
-                    case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
-                        latest = ev.header.timestamp;
-                        break;
-                    case DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED:
-                        mOnDisplayConfigChanged();
-                        break;
-                }
-            }
-        }
-        if (n < 0) {
-            ALOGW("Failed to get events from display event receiver, status=%d", status_t(n));
-        }
-        return latest;
+    virtual void drainPendingEvents() override {
+        AChoreographer_handlePendingEvents(mRenderThread->mChoreographer, mRenderThread);
     }
 
 private:
-    std::unique_ptr<DisplayEventReceiver> mDisplayEventReceiver;
-    std::function<void()> mOnDisplayConfigChanged;
+    RenderThread* mRenderThread;
 };
 
 class DummyVsyncSource : public VsyncSource {
@@ -99,11 +88,14 @@
     DummyVsyncSource(RenderThread* renderThread) : mRenderThread(renderThread) {}
 
     virtual void requestNextVsync() override {
-        mRenderThread->queue().postDelayed(16_ms,
-                                           [this]() { mRenderThread->drainDisplayEventQueue(); });
+        mRenderThread->queue().postDelayed(16_ms, [this]() {
+            RenderThread::frameCallback(systemTime(SYSTEM_TIME_MONOTONIC), mRenderThread);
+        });
     }
 
-    virtual nsecs_t latestVsyncEvent() override { return systemTime(SYSTEM_TIME_MONOTONIC); }
+    virtual void drainPendingEvents() override {
+        RenderThread::frameCallback(systemTime(SYSTEM_TIME_MONOTONIC), mRenderThread);
+    }
 
 private:
     RenderThread* mRenderThread;
@@ -145,29 +137,24 @@
 }
 
 RenderThread::~RenderThread() {
+    // Note that if this fatal assertion is removed then member variables must
+    // be properly destroyed.
     LOG_ALWAYS_FATAL("Can't destroy the render thread");
 }
 
-void RenderThread::initializeDisplayEventReceiver() {
-    LOG_ALWAYS_FATAL_IF(mVsyncSource, "Initializing a second DisplayEventReceiver?");
+void RenderThread::initializeChoreographer() {
+    LOG_ALWAYS_FATAL_IF(mVsyncSource, "Initializing a second Choreographer?");
 
     if (!Properties::isolatedProcess) {
-        auto receiver = std::make_unique<DisplayEventReceiver>(
-            ISurfaceComposer::eVsyncSourceApp,
-            ISurfaceComposer::eConfigChangedDispatch);
-        status_t status = receiver->initCheck();
-        LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
-                            "Initialization of DisplayEventReceiver "
-                            "failed with status: %d",
-                            status);
+        mChoreographer = AChoreographer_create();
+        LOG_ALWAYS_FATAL_IF(mChoreographer == nullptr, "Initialization of Choreographer failed");
+        AChoreographer_registerRefreshRateCallback(mChoreographer,
+                                                   RenderThread::refreshRateCallback, this);
 
         // Register the FD
-        mLooper->addFd(receiver->getFd(), 0, Looper::EVENT_INPUT,
-                       RenderThread::displayEventReceiverCallback, this);
-        mVsyncSource = new DisplayEventReceiverWrapper(std::move(receiver), [this] {
-            DeviceInfo::get()->onDisplayConfigChanged();
-            setupFrameInterval();
-        });
+        mLooper->addFd(AChoreographer_getFd(mChoreographer), 0, Looper::EVENT_INPUT,
+                       RenderThread::choreographerCallback, this);
+        mVsyncSource = new ChoreographerSource(this);
     } else {
         mVsyncSource = new DummyVsyncSource(this);
     }
@@ -175,7 +162,7 @@
 
 void RenderThread::initThreadLocals() {
     setupFrameInterval();
-    initializeDisplayEventReceiver();
+    initializeChoreographer();
     mEglManager = new EglManager();
     mRenderState = new RenderState(*this);
     mVkManager = new VulkanManager();
@@ -183,7 +170,7 @@
 }
 
 void RenderThread::setupFrameInterval() {
-    nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1000000000 / DeviceInfo::getRefreshRate());
+    nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
     mTimeLord.setFrameInterval(frameIntervalNanos);
     mDispatchFrameDelay = static_cast<nsecs_t>(frameIntervalNanos * .25f);
 }
@@ -288,7 +275,7 @@
     }
 }
 
-int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) {
+int RenderThread::choreographerCallback(int fd, int events, void* data) {
     if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
         ALOGE("Display event receiver pipe was closed or an error occurred.  "
               "events=0x%x",
@@ -302,24 +289,10 @@
               events);
         return 1;  // keep the callback
     }
+    RenderThread* rt = reinterpret_cast<RenderThread*>(data);
+    AChoreographer_handlePendingEvents(rt->mChoreographer, data);
 
-    reinterpret_cast<RenderThread*>(data)->drainDisplayEventQueue();
-
-    return 1;  // keep the callback
-}
-
-void RenderThread::drainDisplayEventQueue() {
-    ATRACE_CALL();
-    nsecs_t vsyncEvent = mVsyncSource->latestVsyncEvent();
-    if (vsyncEvent > 0) {
-        mVsyncRequested = false;
-        if (mTimeLord.vsyncReceived(vsyncEvent) && !mFrameCallbackTaskPending) {
-            ATRACE_NAME("queue mFrameCallbackTask");
-            mFrameCallbackTaskPending = true;
-            nsecs_t runAt = (vsyncEvent + mDispatchFrameDelay);
-            queue().postAt(runAt, [this]() { dispatchFrameCallbacks(); });
-        }
-    }
+    return 1;
 }
 
 void RenderThread::dispatchFrameCallbacks() {
@@ -360,7 +333,7 @@
         processQueue();
 
         if (mPendingRegistrationFrameCallbacks.size() && !mFrameCallbackTaskPending) {
-            drainDisplayEventQueue();
+            mVsyncSource->drainPendingEvents();
             mFrameCallbacks.insert(mPendingRegistrationFrameCallbacks.begin(),
                                    mPendingRegistrationFrameCallbacks.end());
             mPendingRegistrationFrameCallbacks.clear();
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index da79e97..8be46a6 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -19,6 +19,7 @@
 
 #include <GrContext.h>
 #include <SkBitmap.h>
+#include <apex/choreographer.h>
 #include <cutils/compiler.h>
 #include <thread/ThreadBase.h>
 #include <utils/Looper.h>
@@ -73,10 +74,11 @@
 
 struct VsyncSource {
     virtual void requestNextVsync() = 0;
-    virtual nsecs_t latestVsyncEvent() = 0;
+    virtual void drainPendingEvents() = 0;
     virtual ~VsyncSource() {}
 };
 
+class ChoreographerSource;
 class DummyVsyncSource;
 
 typedef void (*JVMAttachHook)(const char* name);
@@ -136,6 +138,7 @@
     friend class DispatchFrameCallbacks;
     friend class RenderProxy;
     friend class DummyVsyncSource;
+    friend class ChoreographerSource;
     friend class android::uirenderer::AutoBackendTextureRelease;
     friend class android::uirenderer::TestUtils;
     friend class android::uirenderer::WebViewFunctor;
@@ -149,13 +152,21 @@
     static RenderThread& getInstance();
 
     void initThreadLocals();
-    void initializeDisplayEventReceiver();
+    void initializeChoreographer();
     void setupFrameInterval();
-    static int displayEventReceiverCallback(int fd, int events, void* data);
+    // Callbacks for choreographer events:
+    // choreographerCallback will call AChoreograper_handleEvent to call the
+    // corresponding callbacks for each display event type
+    static int choreographerCallback(int fd, int events, void* data);
+    // Callback that will be run on vsync ticks.
+    static void frameCallback(int64_t frameTimeNanos, void* data);
+    // Callback that will be run whenver there is a refresh rate change.
+    static void refreshRateCallback(int64_t vsyncPeriod, void* data);
     void drainDisplayEventQueue();
     void dispatchFrameCallbacks();
     void requestVsync();
 
+    AChoreographer* mChoreographer;
     VsyncSource* mVsyncSource;
     bool mVsyncRequested;
     std::set<IFrameCallback*> mFrameCallbacks;
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 506d616..4e6b4af 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -156,8 +156,6 @@
     @Nullable
     final Bundle mExtras;
 
-    private final String mUniqueId;
-
     MediaRoute2Info(@NonNull Builder builder) {
         mId = builder.mId;
         mProviderId = builder.mProviderId;
@@ -172,7 +170,6 @@
         mVolumeHandling = builder.mVolumeHandling;
         mDeviceType = builder.mDeviceType;
         mExtras = builder.mExtras;
-        mUniqueId = createUniqueId();
     }
 
     MediaRoute2Info(@NonNull Parcel in) {
@@ -189,18 +186,12 @@
         mVolumeHandling = in.readInt();
         mDeviceType = in.readInt();
         mExtras = in.readBundle();
-        mUniqueId = createUniqueId();
     }
 
-    private String createUniqueId() {
-        String uniqueId = null;
-        if (mProviderId != null) {
-            uniqueId = toUniqueId(mProviderId, mId);
-        }
-        return uniqueId;
-    }
-
-    static String toUniqueId(String providerId, String routeId) {
+    /**
+     * @hide
+     */
+    public static String toUniqueId(String providerId, String routeId) {
         return providerId + ":" + routeId;
     }
 
@@ -251,25 +242,30 @@
     }
 
     /**
-     * Gets the id of the route.
-     * Use {@link #getUniqueId()} if you need a unique identifier.
+     * Gets the id of the route. The routes which are given by {@link MediaRouter2} will have
+     * unique IDs.
+     * <p>
+     * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
+     * can be different from what was set in {@link MediaRoute2ProviderService}.
      *
-     * @see #getUniqueId()
+     * @see Builder#setId(String)
      */
     @NonNull
     public String getId() {
-        return mId;
+        if (mProviderId != null) {
+            return toUniqueId(mProviderId, mId);
+        } else {
+            return mId;
+        }
     }
 
     /**
-     * Gets the unique id of the route. A route obtained from
-     * {@link com.android.server.media.MediaRouterService} always has a unique id.
-     *
-     * @return unique id of the route or null if it has no unique id.
+     * Gets the original id set by {@link Builder#setId(String)}.
+     * @hide
      */
-    @Nullable
-    public String getUniqueId() {
-        return mUniqueId;
+    @NonNull
+    public String getOriginalId() {
+        return mId;
     }
 
     /**
@@ -499,7 +495,15 @@
         }
 
         /**
-         * Sets the unique id of the route.
+         * Sets the unique id of the route. The value given here must be unique for each of your
+         * route.
+         * <p>
+         * In order to ensure uniqueness in {@link MediaRouter2} side, the value of
+         * {@link MediaRoute2Info#getId()} can be different from what was set in
+         * {@link MediaRoute2ProviderService}.
+         * </p>
+         *
+         * @see MediaRoute2Info#getId()
          */
         @NonNull
         public Builder setId(@NonNull String id) {
diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java
index 7078d4a..e2f246c 100644
--- a/media/java/android/media/MediaRoute2ProviderInfo.java
+++ b/media/java/android/media/MediaRoute2ProviderInfo.java
@@ -25,6 +25,7 @@
 
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -161,14 +162,17 @@
                 return this;
             }
             mUniqueId = uniqueId;
-            final int count = mRoutes.size();
-            for (int i = 0; i < count; i++) {
-                MediaRoute2Info route = mRoutes.valueAt(i);
-                mRoutes.setValueAt(i, new MediaRoute2Info.Builder(route)
+
+            final ArrayMap<String, MediaRoute2Info> newRoutes = new ArrayMap<>();
+            for (Map.Entry<String, MediaRoute2Info> entry : mRoutes.entrySet()) {
+                MediaRoute2Info routeWithProviderId = new MediaRoute2Info.Builder(entry.getValue())
                         .setProviderId(mUniqueId)
-                        .build());
+                        .build();
+                newRoutes.put(routeWithProviderId.getId(), routeWithProviderId);
             }
 
+            mRoutes.clear();
+            mRoutes.putAll(newRoutes);
             return this;
         }
 
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index f5cfde4b..bddfa69 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -177,9 +177,9 @@
      * @hide
      */
     public static boolean checkRouteListContainsRouteId(@NonNull List<MediaRoute2Info> routeList,
-            @NonNull String uniqueRouteId) {
+            @NonNull String routeId) {
         for (MediaRoute2Info info : routeList) {
-            if (TextUtils.equals(uniqueRouteId, info.getUniqueId())) {
+            if (TextUtils.equals(routeId, info.getId())) {
                 return true;
             }
         }
@@ -499,7 +499,7 @@
         List<MediaRoute2Info> addedRoutes = new ArrayList<>();
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
-                mRoutes.put(route.getUniqueId(), route);
+                mRoutes.put(route.getId(), route);
                 if (route.supportsControlCategories(mControlCategories)) {
                     addedRoutes.add(route);
                 }
@@ -515,7 +515,7 @@
         List<MediaRoute2Info> removedRoutes = new ArrayList<>();
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
-                mRoutes.remove(route.getUniqueId());
+                mRoutes.remove(route.getId());
                 if (route.supportsControlCategories(mControlCategories)) {
                     removedRoutes.add(route);
                 }
@@ -531,7 +531,7 @@
         List<MediaRoute2Info> changedRoutes = new ArrayList<>();
         synchronized (sRouterLock) {
             for (MediaRoute2Info route : routes) {
-                mRoutes.put(route.getUniqueId(), route);
+                mRoutes.put(route.getId(), route);
                 if (route.supportsControlCategories(mControlCategories)) {
                     changedRoutes.add(route);
                 }
@@ -935,13 +935,13 @@
             }
 
             List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
-            if (checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+            if (checkRouteListContainsRouteId(selectedRoutes, route.getId())) {
                 Log.w(TAG, "Ignoring selecting a route that is already selected. route=" + route);
                 return;
             }
 
             List<MediaRoute2Info> selectableRoutes = getSelectableRoutes();
-            if (!checkRouteListContainsRouteId(selectableRoutes, route.getUniqueId())) {
+            if (!checkRouteListContainsRouteId(selectableRoutes, route.getId())) {
                 Log.w(TAG, "Ignoring selecting a non-selectable route=" + route);
                 return;
             }
@@ -982,13 +982,13 @@
             }
 
             List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
-            if (!checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+            if (!checkRouteListContainsRouteId(selectedRoutes, route.getId())) {
                 Log.w(TAG, "Ignoring deselecting a route that is not selected. route=" + route);
                 return;
             }
 
             List<MediaRoute2Info> deselectableRoutes = getDeselectableRoutes();
-            if (!checkRouteListContainsRouteId(deselectableRoutes, route.getUniqueId())) {
+            if (!checkRouteListContainsRouteId(deselectableRoutes, route.getId())) {
                 Log.w(TAG, "Ignoring deselecting a non-deselectable route=" + route);
                 return;
             }
@@ -1029,14 +1029,14 @@
             }
 
             List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
-            if (checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+            if (checkRouteListContainsRouteId(selectedRoutes, route.getId())) {
                 Log.w(TAG, "Ignoring transferring to a route that is already added. route="
                         + route);
                 return;
             }
 
             List<MediaRoute2Info> transferrableRoutes = getTransferrableRoutes();
-            if (!checkRouteListContainsRouteId(transferrableRoutes, route.getUniqueId())) {
+            if (!checkRouteListContainsRouteId(transferrableRoutes, route.getId())) {
                 Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route);
                 return;
             }
@@ -1084,8 +1084,12 @@
             }
         }
 
+        /**
+         * TODO: Change this to package private. (Hidden for debugging purposes)
+         * @hide
+         */
         @NonNull
-        RouteSessionInfo getRouteSessionInfo() {
+        public RouteSessionInfo getRouteSessionInfo() {
             synchronized (mControllerLock) {
                 return mSessionInfo;
             }
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 2e68e42..3cbbea1 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -295,7 +295,7 @@
     void addRoutesOnHandler(List<MediaRoute2Info> routes) {
         synchronized (mRoutesLock) {
             for (MediaRoute2Info route : routes) {
-                mRoutes.put(route.getUniqueId(), route);
+                mRoutes.put(route.getId(), route);
             }
         }
         if (routes.size() > 0) {
@@ -306,7 +306,7 @@
     void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
         synchronized (mRoutesLock) {
             for (MediaRoute2Info route : routes) {
-                mRoutes.remove(route.getUniqueId());
+                mRoutes.remove(route.getId());
             }
         }
         if (routes.size() > 0) {
@@ -317,7 +317,7 @@
     void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
         synchronized (mRoutesLock) {
             for (MediaRoute2Info route : routes) {
-                mRoutes.put(route.getUniqueId(), route);
+                mRoutes.put(route.getId(), route);
             }
         }
         if (routes.size() > 0) {
diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RouteSessionInfo.java
index 2d7bc24..4a9298a 100644
--- a/media/java/android/media/RouteSessionInfo.java
+++ b/media/java/android/media/RouteSessionInfo.java
@@ -327,14 +327,30 @@
         }
 
         /**
-         * Sets the provider id of the session.
+         * Sets the provider ID of the session.
+         * Also, calling this method will make all type of route IDs be unique by adding
+         * {@code providerId:} as a prefix. So do NOT call this method twice on same instance.
+         *
+         * @hide
          */
         @NonNull
         public Builder setProviderId(String providerId) {
             mProviderId = providerId;
+            convertToUniqueRouteIds(providerId, mSelectedRoutes);
+            convertToUniqueRouteIds(providerId, mSelectableRoutes);
+            convertToUniqueRouteIds(providerId, mDeselectableRoutes);
+            convertToUniqueRouteIds(providerId, mTransferrableRoutes);
             return this;
         }
 
+        private void convertToUniqueRouteIds(@NonNull String providerId,
+                @NonNull List<String> routeIds) {
+            for (int i = 0; i < routeIds.size(); i++) {
+                String routeId = routeIds.get(i);
+                routeIds.set(i, MediaRoute2Info.toUniqueId(providerId, routeId));
+            }
+        }
+
         /**
          * Clears the selected routes.
          */
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index ea00d6e..c17beba 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -61,6 +61,8 @@
     private final boolean mEncrypted;
     private final int mAudioChannelCount;
     private final int mAudioSampleRate;
+    private final boolean mAudioDescription;
+    private final boolean mHardOfHearing;
     private final int mVideoWidth;
     private final int mVideoHeight;
     private final float mVideoFrameRate;
@@ -70,9 +72,9 @@
     private final Bundle mExtra;
 
     private TvTrackInfo(int type, String id, String language, CharSequence description,
-            boolean encrypted, int audioChannelCount, int audioSampleRate, int videoWidth,
-            int videoHeight, float videoFrameRate, float videoPixelAspectRatio,
-            byte videoActiveFormatDescription, Bundle extra) {
+            boolean encrypted, int audioChannelCount, int audioSampleRate, boolean audioDescription,
+            boolean hardOfHearing, int videoWidth, int videoHeight, float videoFrameRate,
+            float videoPixelAspectRatio, byte videoActiveFormatDescription, Bundle extra) {
         mType = type;
         mId = id;
         mLanguage = language;
@@ -80,6 +82,8 @@
         mEncrypted = encrypted;
         mAudioChannelCount = audioChannelCount;
         mAudioSampleRate = audioSampleRate;
+        mAudioDescription = audioDescription;
+        mHardOfHearing = hardOfHearing;
         mVideoWidth = videoWidth;
         mVideoHeight = videoHeight;
         mVideoFrameRate = videoFrameRate;
@@ -96,6 +100,8 @@
         mEncrypted = in.readInt() != 0;
         mAudioChannelCount = in.readInt();
         mAudioSampleRate = in.readInt();
+        mAudioDescription = in.readInt() != 0;
+        mHardOfHearing = in.readInt() != 0;
         mVideoWidth = in.readInt();
         mVideoHeight = in.readInt();
         mVideoFrameRate = in.readFloat();
@@ -172,6 +178,40 @@
     }
 
     /**
+     * Returns {@code true} if the track is an audio description intended for people with visual
+     * impairment, {@code false} otherwise. Valid only for {@link #TYPE_AUDIO} tracks.
+     *
+     * <p>For example of broadcast, audio description information may be referred to broadcast
+     * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio Language
+     * Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN 300 468).
+     *
+     * @throws IllegalStateException if not called on an audio track
+     */
+    public boolean isAudioDescription() {
+        if (mType != TYPE_AUDIO) {
+            throw new IllegalStateException("Not an audio track");
+        }
+        return mAudioDescription;
+    }
+
+    /**
+     * Returns {@code true} if the track is intended for people with hearing impairment, {@code
+     * false} otherwise. Valid only for {@link #TYPE_AUDIO} and {@link #TYPE_SUBTITLE} tracks.
+     *
+     * <p>For example of broadcast, hard of hearing information may be referred to broadcast
+     * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio Language
+     * Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN 300 468).
+     *
+     * @throws IllegalStateException if not called on an audio track or a subtitle track
+     */
+    public boolean isHardOfHearing() {
+        if (mType != TYPE_AUDIO && mType != TYPE_SUBTITLE) {
+            throw new IllegalStateException("Not an audio or a subtitle track");
+        }
+        return mHardOfHearing;
+    }
+
+    /**
      * Returns the width of the video, in the unit of pixels. Valid only for {@link #TYPE_VIDEO}
      * tracks.
      *
@@ -266,6 +306,8 @@
         dest.writeInt(mEncrypted ? 1 : 0);
         dest.writeInt(mAudioChannelCount);
         dest.writeInt(mAudioSampleRate);
+        dest.writeInt(mAudioDescription ? 1 : 0);
+        dest.writeInt(mHardOfHearing ? 1 : 0);
         dest.writeInt(mVideoWidth);
         dest.writeInt(mVideoHeight);
         dest.writeFloat(mVideoFrameRate);
@@ -296,7 +338,9 @@
         switch (mType) {
             case TYPE_AUDIO:
                 return mAudioChannelCount == obj.mAudioChannelCount
-                        && mAudioSampleRate == obj.mAudioSampleRate;
+                        && mAudioSampleRate == obj.mAudioSampleRate
+                        && mAudioDescription == obj.mAudioDescription
+                        && mHardOfHearing == obj.mHardOfHearing;
 
             case TYPE_VIDEO:
                 return mVideoWidth == obj.mVideoWidth
@@ -304,6 +348,9 @@
                         && mVideoFrameRate == obj.mVideoFrameRate
                         && mVideoPixelAspectRatio == obj.mVideoPixelAspectRatio
                         && mVideoActiveFormatDescription == obj.mVideoActiveFormatDescription;
+
+            case TYPE_SUBTITLE:
+                return mHardOfHearing == obj.mHardOfHearing;
         }
 
         return true;
@@ -338,6 +385,8 @@
         private boolean mEncrypted;
         private int mAudioChannelCount;
         private int mAudioSampleRate;
+        private boolean mAudioDescription;
+        private boolean mHardOfHearing;
         private int mVideoWidth;
         private int mVideoHeight;
         private float mVideoFrameRate;
@@ -430,6 +479,48 @@
         }
 
         /**
+         * Sets the audio description attribute of the audio. Valid only for {@link #TYPE_AUDIO}
+         * tracks.
+         *
+         * <p>For example of broadcast, audio description information may be referred to broadcast
+         * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio
+         * Language Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN
+         * 300 468).
+         *
+         * @param audioDescription The audio description attribute of the audio.
+         * @throws IllegalStateException if not called on an audio track
+         */
+        @NonNull
+        public Builder setAudioDescription(boolean audioDescription) {
+            if (mType != TYPE_AUDIO) {
+                throw new IllegalStateException("Not an audio track");
+            }
+            mAudioDescription = audioDescription;
+            return this;
+        }
+
+        /**
+         * Sets the hard of hearing attribute of the track. Valid only for {@link #TYPE_AUDIO} and
+         * {@link #TYPE_SUBTITLE} tracks.
+         *
+         * <p>For example of broadcast, hard of hearing information may be referred to broadcast
+         * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio
+         * Language Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN
+         * 300 468).
+         *
+         * @param hardOfHearing The hard of hearing attribute of the track.
+         * @throws IllegalStateException if not called on an audio track or a subtitle track
+         */
+        @NonNull
+        public Builder setHardOfHearing(boolean hardOfHearing) {
+            if (mType != TYPE_AUDIO && mType != TYPE_SUBTITLE) {
+                throw new IllegalStateException("Not an audio track or a subtitle track");
+            }
+            mHardOfHearing = hardOfHearing;
+            return this;
+        }
+
+        /**
          * Sets the width of the video, in the unit of pixels. Valid only for {@link #TYPE_VIDEO}
          * tracks.
          *
@@ -531,8 +622,9 @@
          */
         public TvTrackInfo build() {
             return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncrypted,
-                    mAudioChannelCount, mAudioSampleRate, mVideoWidth, mVideoHeight,
-                    mVideoFrameRate, mVideoPixelAspectRatio, mVideoActiveFormatDescription, mExtra);
+                    mAudioChannelCount, mAudioSampleRate, mAudioDescription, mHardOfHearing,
+                    mVideoWidth, mVideoHeight, mVideoFrameRate, mVideoPixelAspectRatio,
+                    mVideoActiveFormatDescription, mExtra);
         }
     }
 }
diff --git a/media/java/android/media/tv/tuner/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java
new file mode 100644
index 0000000..bda166e
--- /dev/null
+++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2019 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.media.tv.tuner;
+
+/**
+ * Capabilities info for Demux.
+ * @hide
+ */
+public class DemuxCapabilities {
+    private final int mNumDemux;
+    private final int mNumRecord;
+    private final int mNumPlayback;
+    private final int mNumTsFilter;
+    private final int mNumSectionFilter;
+    private final int mNumAudioFilter;
+    private final int mNumVideoFilter;
+    private final int mNumPesFilter;
+    private final int mNumPcrFilter;
+    private final int mNumBytesInSectionFilter;
+    private final int mFilterCaps;
+    private final int[] mLinkCaps;
+
+    DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter,
+            int numSectionFilter, int numAudioFilter, int numVideoFilter, int numPesFilter,
+            int numPcrFilter, int numBytesInSectionFilter, int filterCaps, int[] linkCaps) {
+        mNumDemux = numDemux;
+        mNumRecord = numRecord;
+        mNumPlayback = numPlayback;
+        mNumTsFilter = numTsFilter;
+        mNumSectionFilter = numSectionFilter;
+        mNumAudioFilter = numAudioFilter;
+        mNumVideoFilter = numVideoFilter;
+        mNumPesFilter = numPesFilter;
+        mNumPcrFilter = numPcrFilter;
+        mNumBytesInSectionFilter = numBytesInSectionFilter;
+        mFilterCaps = filterCaps;
+        mLinkCaps = linkCaps;
+    }
+
+    /** Gets total number of demuxes. */
+    public int getNumDemux() {
+        return mNumDemux;
+    }
+    /** Gets max number of recordings at a time. */
+    public int getNumRecord() {
+        return mNumRecord;
+    }
+    /** Gets max number of playbacks at a time. */
+    public int getNumPlayback() {
+        return mNumPlayback;
+    }
+    /** Gets number of TS filters. */
+    public int getNumTsFilter() {
+        return mNumTsFilter;
+    }
+    /** Gets number of section filters. */
+    public int getNumSectionFilter() {
+        return mNumSectionFilter;
+    }
+    /** Gets number of audio filters. */
+    public int getNumAudioFilter() {
+        return mNumAudioFilter;
+    }
+    /** Gets number of video filters. */
+    public int getNumVideoFilter() {
+        return mNumVideoFilter;
+    }
+    /** Gets number of PES filters. */
+    public int getNumPesFilter() {
+        return mNumPesFilter;
+    }
+    /** Gets number of PCR filters. */
+    public int getNumPcrFilter() {
+        return mNumPcrFilter;
+    }
+    /** Gets number of bytes in the mask of a section filter. */
+    public int getNumBytesInSectionFilter() {
+        return mNumBytesInSectionFilter;
+    }
+    /**
+     * Gets filter capabilities in bit field.
+     *
+     * The bits of the returned value is corresponding to the types in
+     * {@link TunerConstants.FilterType}.
+     */
+    public int getFilterCapabilities() {
+        return mFilterCaps;
+    }
+
+    /**
+     * Gets link capabilities.
+     *
+     * The returned array contains the same elements as the number of types in
+     * {@link TunerConstants.FilterType}.
+     * The ith element represents the filter's capability as the source for the ith type
+     */
+    public int[] getLinkCapabilities() {
+        return mLinkCaps;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/FilterSettings.java b/media/java/android/media/tv/tuner/FilterConfiguration.java
similarity index 84%
rename from media/java/android/media/tv/tuner/FilterSettings.java
rename to media/java/android/media/tv/tuner/FilterConfiguration.java
index 4ee61a8..b80a82a 100644
--- a/media/java/android/media/tv/tuner/FilterSettings.java
+++ b/media/java/android/media/tv/tuner/FilterConfiguration.java
@@ -22,20 +22,20 @@
 import java.util.List;
 
 /**
- * Demux Filter settings.
+ * Demux Filter configuration.
  *
  * @hide
  */
-public abstract class FilterSettings {
+public abstract class FilterConfiguration {
     @Nullable
     protected final Settings mSettings;
 
-    protected FilterSettings(Settings settings) {
+    protected FilterConfiguration(Settings settings) {
         mSettings = settings;
     }
 
     /**
-     * Gets filter settings type
+     * Gets filter configuration type
      */
     @FilterType
     public abstract int getType();
@@ -47,12 +47,12 @@
     // TODO: more builders and getters
 
     /**
-     *  Filter Settings for a TS filter.
+     *  Filter configuration for a TS filter.
      */
-    public static class TsFilterSettings extends FilterSettings {
+    public static class TsFilterConfiguration extends FilterConfiguration {
         private int mTpid;
 
-        private TsFilterSettings(Settings settings, int tpid) {
+        private TsFilterConfiguration(Settings settings, int tpid) {
             super(settings);
             mTpid = tpid;
         }
@@ -70,7 +70,7 @@
         }
 
         /**
-         * Builder for TsFilterSettings.
+         * Builder for TsFilterConfiguration.
          */
         public static class Builder {
             private Settings mSettings;
@@ -93,21 +93,21 @@
             }
 
             /**
-             * Builds a TsFilterSettings instance.
+             * Builds a TsFilterConfiguration instance.
              */
-            public TsFilterSettings build() {
-                return new TsFilterSettings(mSettings, mTpid);
+            public TsFilterConfiguration build() {
+                return new TsFilterConfiguration(mSettings, mTpid);
             }
         }
     }
 
     /**
-     *  Filter Settings for a MMTP filter.
+     *  Filter configuration for a MMTP filter.
      */
-    public static class MmtpFilterSettings extends FilterSettings {
+    public static class MmtpFilterConfiguration extends FilterConfiguration {
         private int mMmtpPid;
 
-        public MmtpFilterSettings(Settings settings) {
+        public MmtpFilterConfiguration(Settings settings) {
             super(settings);
         }
 
@@ -119,16 +119,16 @@
 
 
     /**
-     *  Filter Settings for a IP filter.
+     *  Filter configuration for a IP filter.
      */
-    public static class IpFilterSettings extends FilterSettings {
+    public static class IpFilterConfiguration extends FilterConfiguration {
         private byte[] mSrcIpAddress;
         private byte[] mDstIpAddress;
         private int mSrcPort;
         private int mDstPort;
         private boolean mPassthrough;
 
-        public IpFilterSettings(Settings settings) {
+        public IpFilterConfiguration(Settings settings) {
             super(settings);
         }
 
@@ -140,14 +140,14 @@
 
 
     /**
-     *  Filter Settings for a TLV filter.
+     *  Filter configuration for a TLV filter.
      */
-    public static class TlvFilterSettings extends FilterSettings {
+    public static class TlvFilterConfiguration extends FilterConfiguration {
         private int mPacketType;
         private boolean mIsCompressedIpPacket;
         private boolean mPassthrough;
 
-        public TlvFilterSettings(Settings settings) {
+        public TlvFilterConfiguration(Settings settings) {
             super(settings);
         }
 
@@ -159,13 +159,13 @@
 
 
     /**
-     *  Filter Settings for a ALP filter.
+     *  Filter configuration for a ALP filter.
      */
-    public static class AlpFilterSettings extends FilterSettings {
+    public static class AlpFilterConfiguration extends FilterConfiguration {
         private int mPacketType;
         private int mLengthType;
 
-        public AlpFilterSettings(Settings settings) {
+        public AlpFilterConfiguration(Settings settings) {
             super(settings);
         }
 
diff --git a/media/java/android/media/tv/tuner/FrontendSettings.java b/media/java/android/media/tv/tuner/FrontendSettings.java
index 531e210..e2e9910 100644
--- a/media/java/android/media/tv/tuner/FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/FrontendSettings.java
@@ -19,8 +19,6 @@
 import android.annotation.SystemApi;
 import android.media.tv.tuner.TunerConstants.FrontendSettingsType;
 
-import java.util.List;
-
 /**
  * Frontend settings for tune and scan operations.
  * @hide
@@ -29,7 +27,8 @@
 public abstract class FrontendSettings {
     private final int mFrequency;
 
-    FrontendSettings(int frequency) {
+    /** @hide */
+    public FrontendSettings(int frequency) {
         mFrequency = frequency;
     }
 
@@ -48,282 +47,4 @@
         return mFrequency;
     }
 
-    // TODO: use hal constants for enum fields
-    // TODO: javaDoc
-    // TODO: add builders and getters for other settings type
-
-    /**
-     * Frontend settings for analog.
-     * @hide
-     */
-    public static class FrontendAnalogSettings extends FrontendSettings {
-        private int mAnalogType;
-        private int mSifStandard;
-
-        @Override
-        public int getType() {
-            return TunerConstants.FRONTEND_TYPE_ANALOG;
-        }
-
-        public int getAnalogType() {
-            return mAnalogType;
-        }
-
-        public int getSifStandard() {
-            return mSifStandard;
-        }
-
-        /**
-         * Creates a new builder object.
-         */
-        public static Builder newBuilder() {
-            return new Builder();
-        }
-
-        private FrontendAnalogSettings(int frequency, int analogType, int sifStandard) {
-            super(frequency);
-            mAnalogType = analogType;
-            mSifStandard = sifStandard;
-        }
-
-        /**
-         * Builder for FrontendAnalogSettings.
-         */
-        public static class Builder {
-            private int mFrequency;
-            private int mAnalogType;
-            private int mSifStandard;
-
-            private Builder() {}
-
-            /**
-             * Sets frequency.
-             */
-            public Builder setFrequency(int frequency) {
-                mFrequency = frequency;
-                return this;
-            }
-
-            /**
-             * Sets analog type.
-             */
-            public Builder setAnalogType(int analogType) {
-                mAnalogType = analogType;
-                return this;
-            }
-
-            /**
-             * Sets sif standard.
-             */
-            public Builder setSifStandard(int sifStandard) {
-                mSifStandard = sifStandard;
-                return this;
-            }
-
-            /**
-             * Builds a FrontendAnalogSettings instance.
-             */
-            public FrontendAnalogSettings build() {
-                return new FrontendAnalogSettings(mFrequency, mAnalogType, mSifStandard);
-            }
-        }
-    }
-
-    /**
-     * Frontend settings for ATSC.
-     * @hide
-     */
-    public static class FrontendAtscSettings extends FrontendSettings {
-        public int modulation;
-
-        FrontendAtscSettings(int frequency) {
-            super(frequency);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FRONTEND_TYPE_ATSC;
-        }
-    }
-
-    /**
-     * Frontend settings for ATSC-3.
-     * @hide
-     */
-    public static class FrontendAtsc3Settings extends FrontendSettings {
-        public int bandwidth;
-        public byte demodOutputFormat;
-        public List<FrontendAtsc3PlpSettings> plpSettings;
-
-        FrontendAtsc3Settings(int frequency) {
-            super(frequency);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FRONTEND_TYPE_ATSC3;
-        }
-    }
-
-    /**
-     * Frontend settings for DVBS.
-     * @hide
-     */
-    public static class FrontendDvbsSettings extends FrontendSettings {
-        public int modulation;
-        public FrontendDvbsCodeRate coderate;
-        public int symbolRate;
-        public int rolloff;
-        public int pilot;
-        public int inputStreamId;
-        public byte standard;
-
-        FrontendDvbsSettings(int frequency) {
-            super(frequency);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FRONTEND_TYPE_DVBS;
-        }
-    }
-
-    /**
-     * Frontend settings for DVBC.
-     * @hide
-     */
-    public static class FrontendDvbcSettings extends FrontendSettings {
-        public int modulation;
-        public long fec;
-        public int symbolRate;
-        public int outerFec;
-        public byte annex;
-        public int spectralInversion;
-
-        FrontendDvbcSettings(int frequency) {
-            super(frequency);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FRONTEND_TYPE_DVBC;
-        }
-    }
-
-    /**
-     * Frontend settings for DVBT.
-     * @hide
-     */
-    public static class FrontendDvbtSettings extends FrontendSettings {
-        public int transmissionMode;
-        public int bandwidth;
-        public int constellation;
-        public int hierarchy;
-        public int hpCoderate;
-        public int lpCoderate;
-        public int guardInterval;
-        public boolean isHighPriority;
-        public byte standard;
-        public boolean isMiso;
-        public int plpMode;
-        public byte plpId;
-        public byte plpGroupId;
-
-        FrontendDvbtSettings(int frequency) {
-            super(frequency);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FRONTEND_TYPE_DVBT;
-        }
-    }
-
-    /**
-     * Frontend settings for ISDBS.
-     * @hide
-     */
-    public static class FrontendIsdbsSettings extends FrontendSettings {
-        public int streamId;
-        public int streamIdType;
-        public int modulation;
-        public int coderate;
-        public int symbolRate;
-        public int rolloff;
-
-        FrontendIsdbsSettings(int frequency) {
-            super(frequency);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FRONTEND_TYPE_ISDBS;
-        }
-    }
-
-    /**
-     * Frontend settings for ISDBS-3.
-     * @hide
-     */
-    public static class FrontendIsdbs3Settings extends FrontendSettings {
-        public int streamId;
-        public int streamIdType;
-        public int modulation;
-        public int coderate;
-        public int symbolRate;
-        public int rolloff;
-
-        FrontendIsdbs3Settings(int frequency) {
-            super(frequency);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FRONTEND_TYPE_ISDBS3;
-        }
-    }
-
-    /**
-     * Frontend settings for ISDBT.
-     * @hide
-     */
-    public static class FrontendIsdbtSettings extends FrontendSettings {
-        public int modulation;
-        public int bandwidth;
-        public int coderate;
-        public int guardInterval;
-        public int serviceAreaId;
-
-        FrontendIsdbtSettings(int frequency) {
-            super(frequency);
-        }
-
-        @Override
-        public int getType() {
-            return TunerConstants.FRONTEND_TYPE_ISDBT;
-        }
-    }
-
-    /**
-     * PLP settings for ATSC-3.
-     * @hide
-     */
-    public static class FrontendAtsc3PlpSettings {
-        public byte plpId;
-        public int modulation;
-        public int interleaveMode;
-        public int codeRate;
-        public int fec;
-    }
-
-    /**
-     * Code rate for DVBS.
-     * @hide
-     */
-    public static class FrontendDvbsCodeRate {
-        public long fec;
-        public boolean isLinear;
-        public boolean isShortFrames;
-        public int bitsPer1000Symbol;
-    }
 }
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 63ef4b8..43f9a89 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -21,8 +21,9 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.Context;
-import android.media.tv.tuner.FilterSettings.Settings;
+import android.media.tv.tuner.FilterConfiguration.Settings;
 import android.media.tv.tuner.TunerConstants.DemuxPidType;
+import android.media.tv.tuner.TunerConstants.FilterStatus;
 import android.media.tv.tuner.TunerConstants.FilterSubtype;
 import android.media.tv.tuner.TunerConstants.FilterType;
 import android.media.tv.tuner.TunerConstants.FrontendScanType;
@@ -31,6 +32,9 @@
 import android.media.tv.tuner.TunerConstants.LnbVoltage;
 import android.media.tv.tuner.TunerConstants.Result;
 import android.media.tv.tuner.filter.FilterEvent;
+import android.media.tv.tuner.frontend.FrontendCallback;
+import android.media.tv.tuner.frontend.FrontendInfo;
+import android.media.tv.tuner.frontend.FrontendStatus;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -127,24 +131,7 @@
 
     private native Dvr nativeOpenDvr(int type, int bufferSize);
 
-    /**
-     * Frontend Callback.
-     *
-     * @hide
-     */
-    public interface FrontendCallback {
-
-        /**
-         * Invoked when there is a frontend event.
-         */
-        void onEvent(int frontendEventType);
-
-        /**
-         * Invoked when there is a scan message.
-         * @param msg
-         */
-        void onScanMessage(ScanMessage msg);
-    }
+    private static native DemuxCapabilities nativeGetDemuxCapabilities();
 
     /**
      * LNB Callback.
@@ -168,19 +155,23 @@
     }
 
     /**
-     * Frontend Callback.
-     *
-     * @hide
+     * Callback interface for receiving information from the corresponding filters.
      */
     public interface FilterCallback {
         /**
          * Invoked when there are filter events.
+         *
+         * @param filter the corresponding filter which sent the events.
+         * @param events the filter events sent from the filter.
          */
-        void onFilterEvent(FilterEvent[] events);
+        void onFilterEvent(@NonNull Filter filter, @NonNull FilterEvent[] events);
         /**
          * Invoked when filter status changed.
+         *
+         * @param filter the corresponding filter whose status is changed.
+         * @param status the new status of the filter.
          */
-        void onFilterStatus(int status);
+        void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status);
     }
 
     /**
@@ -226,7 +217,7 @@
                 case MSG_ON_FILTER_STATUS: {
                     Filter filter = (Filter) msg.obj;
                     if (filter.mCallback != null) {
-                        filter.mCallback.onFilterStatus(msg.arg1);
+                        filter.mCallback.onFilterStatusChanged(filter, msg.arg1);
                     }
                     break;
                 }
@@ -434,6 +425,11 @@
         return mFrontend.mId;
     }
 
+    /** @hide */
+    private static DemuxCapabilities getDemuxCapabilities() {
+        return nativeGetDemuxCapabilities();
+    }
+
     private List<Integer> getFrontendIds() {
         mFrontendIds = nativeGetFrontendIds();
         return mFrontendIds;
@@ -456,13 +452,18 @@
         }
     }
 
-    /** @hide */
+    /**
+     * Tuner data filter.
+     *
+     * <p> This class is used to filter wanted data according to the filter's configuration.
+     */
     public class Filter {
         private long mNativeContext;
         private FilterCallback mCallback;
         int mId;
 
-        private native int nativeConfigureFilter(int type, int subType, FilterSettings settings);
+        private native int nativeConfigureFilter(
+                int type, int subType, FilterConfiguration settings);
         private native int nativeGetId();
         private native int nativeSetDataSource(Filter source);
         private native int nativeStartFilter();
@@ -487,8 +488,9 @@
          *
          * @param settings the settings of the filter.
          * @return result status of the operation.
+         * @hide
          */
-        public int configure(FilterSettings settings) {
+        public int configure(FilterConfiguration settings) {
             int subType = -1;
             Settings s = settings.getSettings();
             if (s != null) {
@@ -501,6 +503,7 @@
          * Gets the filter Id.
          *
          * @return the hardware resource Id for the filter.
+         * @hide
          */
         public int getId() {
             return nativeGetId();
@@ -517,6 +520,7 @@
          * @param source the filter instance which provides data input. Switch to
          * use demux as data source if the filter instance is NULL.
          * @return result status of the operation.
+         * @hide
          */
         public int setDataSource(@Nullable Filter source) {
             return nativeSetDataSource(source);
@@ -526,6 +530,7 @@
          * Starts the filter.
          *
          * @return result status of the operation.
+         * @hide
          */
         public int start() {
             return nativeStartFilter();
@@ -536,6 +541,7 @@
          * Stops the filter.
          *
          * @return result status of the operation.
+         * @hide
          */
         public int stop() {
             return nativeStopFilter();
@@ -545,11 +551,13 @@
          * Flushes the filter.
          *
          * @return result status of the operation.
+         * @hide
          */
         public int flush() {
             return nativeFlushFilter();
         }
 
+        /** @hide */
         public int read(@NonNull byte[] buffer, int offset, int size) {
             size = Math.min(size, buffer.length - offset);
             return nativeRead(buffer, offset, size);
@@ -559,6 +567,7 @@
          * Release the Filter instance.
          *
          * @return result status of the operation.
+         * @hide
          */
         public int close() {
             return nativeClose();
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index e8e4b22..d24e582 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -18,16 +18,22 @@
 
 import android.annotation.IntDef;
 import android.annotation.LongDef;
+import android.annotation.SystemApi;
 import android.hardware.tv.tuner.V1_0.Constants;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
 /**
+ * Constants for tuner framework.
+ *
  * @hide
  */
+@SystemApi
 public final class TunerConstants {
+    /** @hide */
     public static final int INVALID_TS_PID = Constants.Constant.INVALID_TS_PID;
+    /** @hide */
     public static final int INVALID_STREAM_ID = Constants.Constant.INVALID_STREAM_ID;
 
 
@@ -180,6 +186,37 @@
     public static final int FILTER_SUBTYPE_PTP = 16;
 
     /** @hide */
+    @IntDef({FILTER_STATUS_DATA_READY, FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER,
+            FILTER_STATUS_OVERFLOW})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FilterStatus {}
+
+    /**
+     * The status of a filter that the data in the filter buffer is ready to be read.
+     */
+    public static final int FILTER_STATUS_DATA_READY = Constants.DemuxFilterStatus.DATA_READY;
+    /**
+     * The status of a filter that the amount of available data in the filter buffer is at low
+     * level.
+     *
+     * The value is set to 25 percent of the buffer size by default. It can be changed when
+     * configuring the filter.
+     */
+    public static final int FILTER_STATUS_LOW_WATER = Constants.DemuxFilterStatus.LOW_WATER;
+    /**
+     * The status of a filter that the amount of available data in the filter buffer is at high
+     * level.
+     * The value is set to 75 percent of the buffer size by default. It can be changed when
+     * configuring the filter.
+     */
+    public static final int FILTER_STATUS_HIGH_WATER = Constants.DemuxFilterStatus.HIGH_WATER;
+    /**
+     * The status of a filter that the filter buffer is full and newly filtered data is being
+     * discarded.
+     */
+    public static final int FILTER_STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW;
+
+    /** @hide */
     @IntDef({FRONTEND_SCAN_UNDEFINED, FRONTEND_SCAN_AUTO, FRONTEND_SCAN_BLIND})
     @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendScanType {}
@@ -739,119 +776,162 @@
     public static final int HIERARCHY_4_INDEPTH =
             Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_ANALOG_TYPE_UNDEFINED, FRONTEND_ANALOG_TYPE_PAL, FRONTEND_ANALOG_TYPE_SECAM,
             FRONTEND_ANALOG_TYPE_NTSC})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendAnalogType {}
-
+    /** @hide */
     public static final int FRONTEND_ANALOG_TYPE_UNDEFINED = Constants.FrontendAnalogType.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_ANALOG_TYPE_PAL = Constants.FrontendAnalogType.PAL;
+    /** @hide */
     public static final int FRONTEND_ANALOG_TYPE_SECAM = Constants.FrontendAnalogType.SECAM;
+    /** @hide */
     public static final int FRONTEND_ANALOG_TYPE_NTSC = Constants.FrontendAnalogType.NTSC;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_ANALOG_SIF_UNDEFINED, FRONTEND_ANALOG_SIF_BG, FRONTEND_ANALOG_SIF_BG_A2,
             FRONTEND_ANALOG_SIF_BG_NICAM, FRONTEND_ANALOG_SIF_I, FRONTEND_ANALOG_SIF_DK,
             FRONTEND_ANALOG_SIF_DK1, FRONTEND_ANALOG_SIF_DK2, FRONTEND_ANALOG_SIF_DK3,
             FRONTEND_ANALOG_SIF_DK_NICAM, FRONTEND_ANALOG_SIF_L, FRONTEND_ANALOG_SIF_M,
             FRONTEND_ANALOG_SIF_M_BTSC, FRONTEND_ANALOG_SIF_M_A2, FRONTEND_ANALOG_SIF_M_EIA_J,
             FRONTEND_ANALOG_SIF_I_NICAM, FRONTEND_ANALOG_SIF_L_NICAM, FRONTEND_ANALOG_SIF_L_PRIME})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendAnalogSifStandard {}
-
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_UNDEFINED =
             Constants.FrontendAnalogSifStandard.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_BG = Constants.FrontendAnalogSifStandard.BG;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_BG_A2 = Constants.FrontendAnalogSifStandard.BG_A2;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_BG_NICAM =
             Constants.FrontendAnalogSifStandard.BG_NICAM;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_I = Constants.FrontendAnalogSifStandard.I;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_DK = Constants.FrontendAnalogSifStandard.DK;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_DK1 = Constants.FrontendAnalogSifStandard.DK1;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_DK2 = Constants.FrontendAnalogSifStandard.DK2;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_DK3 = Constants.FrontendAnalogSifStandard.DK3;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_DK_NICAM =
             Constants.FrontendAnalogSifStandard.DK_NICAM;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_L = Constants.FrontendAnalogSifStandard.L;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_M = Constants.FrontendAnalogSifStandard.M;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_M_BTSC = Constants.FrontendAnalogSifStandard.M_BTSC;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_M_A2 = Constants.FrontendAnalogSifStandard.M_A2;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_M_EIA_J =
             Constants.FrontendAnalogSifStandard.M_EIA_J;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_I_NICAM =
             Constants.FrontendAnalogSifStandard.I_NICAM;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_L_NICAM =
             Constants.FrontendAnalogSifStandard.L_NICAM;
+    /** @hide */
     public static final int FRONTEND_ANALOG_SIF_L_PRIME =
             Constants.FrontendAnalogSifStandard.L_PRIME;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_ATSC_MODULATION_UNDEFINED, FRONTEND_ATSC_MODULATION_AUTO,
             FRONTEND_ATSC_MODULATION_MOD_8VSB, FRONTEND_ATSC_MODULATION_MOD_16VSB})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendAtscModulation {}
-
+    /** @hide */
     public static final int FRONTEND_ATSC_MODULATION_UNDEFINED =
             Constants.FrontendAtscModulation.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_ATSC_MODULATION_AUTO = Constants.FrontendAtscModulation.AUTO;
+    /** @hide */
     public static final int FRONTEND_ATSC_MODULATION_MOD_8VSB =
             Constants.FrontendAtscModulation.MOD_8VSB;
+    /** @hide */
     public static final int FRONTEND_ATSC_MODULATION_MOD_16VSB =
             Constants.FrontendAtscModulation.MOD_16VSB;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_ATSC3_BANDWIDTH_UNDEFINED, FRONTEND_ATSC3_BANDWIDTH_AUTO,
             FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_6MHZ, FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_7MHZ,
             FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_8MHZ})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendAtsc3Bandwidth {}
-
+    /** @hide */
     public static final int FRONTEND_ATSC3_BANDWIDTH_UNDEFINED =
             Constants.FrontendAtsc3Bandwidth.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_ATSC3_BANDWIDTH_AUTO = Constants.FrontendAtsc3Bandwidth.AUTO;
+    /** @hide */
     public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_6MHZ =
             Constants.FrontendAtsc3Bandwidth.BANDWIDTH_6MHZ;
+    /** @hide */
     public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_7MHZ =
             Constants.FrontendAtsc3Bandwidth.BANDWIDTH_7MHZ;
+    /** @hide */
     public static final int FRONTEND_ATSC3_BANDWIDTH_BANDWIDTH_8MHZ =
             Constants.FrontendAtsc3Bandwidth.BANDWIDTH_8MHZ;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_ATSC3_MODULATION_UNDEFINED, FRONTEND_ATSC3_MODULATION_AUTO,
             FRONTEND_ATSC3_MODULATION_MOD_QPSK, FRONTEND_ATSC3_MODULATION_MOD_16QAM,
             FRONTEND_ATSC3_MODULATION_MOD_64QAM, FRONTEND_ATSC3_MODULATION_MOD_256QAM,
             FRONTEND_ATSC3_MODULATION_MOD_1024QAM, FRONTEND_ATSC3_MODULATION_MOD_4096QAM})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendAtsc3Modulation {}
-
+    /** @hide */
     public static final int FRONTEND_ATSC3_MODULATION_UNDEFINED =
             Constants.FrontendAtsc3Modulation.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_ATSC3_MODULATION_AUTO = Constants.FrontendAtsc3Modulation.AUTO;
+    /** @hide */
     public static final int FRONTEND_ATSC3_MODULATION_MOD_QPSK =
             Constants.FrontendAtsc3Modulation.MOD_QPSK;
+    /** @hide */
     public static final int FRONTEND_ATSC3_MODULATION_MOD_16QAM =
             Constants.FrontendAtsc3Modulation.MOD_16QAM;
+    /** @hide */
     public static final int FRONTEND_ATSC3_MODULATION_MOD_64QAM =
             Constants.FrontendAtsc3Modulation.MOD_64QAM;
+    /** @hide */
     public static final int FRONTEND_ATSC3_MODULATION_MOD_256QAM =
             Constants.FrontendAtsc3Modulation.MOD_256QAM;
+    /** @hide */
     public static final int FRONTEND_ATSC3_MODULATION_MOD_1024QAM =
             Constants.FrontendAtsc3Modulation.MOD_1024QAM;
+    /** @hide */
     public static final int FRONTEND_ATSC3_MODULATION_MOD_4096QAM =
             Constants.FrontendAtsc3Modulation.MOD_4096QAM;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_UNDEFINED,
             FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_AUTO, FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_CTI,
             FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_HTI})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendAtsc3TimeInterleaveMode {}
-
+    /** @hide */
     public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_UNDEFINED =
             Constants.FrontendAtsc3TimeInterleaveMode.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_AUTO =
             Constants.FrontendAtsc3TimeInterleaveMode.AUTO;
+    /** @hide */
     public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_CTI =
             Constants.FrontendAtsc3TimeInterleaveMode.CTI;
+    /** @hide */
     public static final int FRONTEND_ATSC3_TIME_INTERLEAVE_MODE_HTI =
             Constants.FrontendAtsc3TimeInterleaveMode.HTI;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_ATSC3_CODERATE_UNDEFINED, FRONTEND_ATSC3_CODERATE_AUTO,
             FRONTEND_ATSC3_CODERATE_2_15, FRONTEND_ATSC3_CODERATE_3_15,
             FRONTEND_ATSC3_CODERATE_4_15, FRONTEND_ATSC3_CODERATE_5_15,
@@ -859,258 +939,350 @@
             FRONTEND_ATSC3_CODERATE_8_15, FRONTEND_ATSC3_CODERATE_9_15,
             FRONTEND_ATSC3_CODERATE_10_15, FRONTEND_ATSC3_CODERATE_11_15,
             FRONTEND_ATSC3_CODERATE_12_15, FRONTEND_ATSC3_CODERATE_13_15})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendAtsc3CodeRate {}
-
+    /** @hide */
     public static final int FRONTEND_ATSC3_CODERATE_UNDEFINED =
             Constants.FrontendAtsc3CodeRate.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_ATSC3_CODERATE_AUTO = Constants.FrontendAtsc3CodeRate.AUTO;
+    /** @hide */
     public static final int FRONTEND_ATSC3_CODERATE_2_15 =
             Constants.FrontendAtsc3CodeRate.CODERATE_2_15;
+    /** @hide */
     public static final int FRONTEND_ATSC3_CODERATE_3_15 =
             Constants.FrontendAtsc3CodeRate.CODERATE_3_15;
+    /** @hide */
     public static final int FRONTEND_ATSC3_CODERATE_4_15 =
             Constants.FrontendAtsc3CodeRate.CODERATE_4_15;
+    /** @hide */
     public static final int FRONTEND_ATSC3_CODERATE_5_15 =
             Constants.FrontendAtsc3CodeRate.CODERATE_5_15;
+    /** @hide */
     public static final int FRONTEND_ATSC3_CODERATE_6_15 =
             Constants.FrontendAtsc3CodeRate.CODERATE_6_15;
+    /** @hide */
     public static final int FRONTEND_ATSC3_CODERATE_7_15 =
             Constants.FrontendAtsc3CodeRate.CODERATE_7_15;
+    /** @hide */
     public static final int FRONTEND_ATSC3_CODERATE_8_15 =
             Constants.FrontendAtsc3CodeRate.CODERATE_8_15;
+    /** @hide */
     public static final int FRONTEND_ATSC3_CODERATE_9_15 =
             Constants.FrontendAtsc3CodeRate.CODERATE_9_15;
+    /** @hide */
     public static final int FRONTEND_ATSC3_CODERATE_10_15 =
             Constants.FrontendAtsc3CodeRate.CODERATE_10_15;
+    /** @hide */
     public static final int FRONTEND_ATSC3_CODERATE_11_15 =
             Constants.FrontendAtsc3CodeRate.CODERATE_11_15;
+    /** @hide */
     public static final int FRONTEND_ATSC3_CODERATE_12_15 =
             Constants.FrontendAtsc3CodeRate.CODERATE_12_15;
+    /** @hide */
     public static final int FRONTEND_ATSC3_CODERATE_13_15 =
             Constants.FrontendAtsc3CodeRate.CODERATE_13_15;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_ATSC3_FEC_UNDEFINED, FRONTEND_ATSC3_FEC_AUTO, FRONTEND_ATSC3_FEC_BCH_LDPC_16K,
             FRONTEND_ATSC3_FEC_BCH_LDPC_64K, FRONTEND_ATSC3_FEC_CRC_LDPC_16K,
             FRONTEND_ATSC3_FEC_CRC_LDPC_64K, FRONTEND_ATSC3_FEC_LDPC_16K,
             FRONTEND_ATSC3_FEC_LDPC_64K})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendAtsc3Fec {}
-
+    /** @hide */
     public static final int FRONTEND_ATSC3_FEC_UNDEFINED = Constants.FrontendAtsc3Fec.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_ATSC3_FEC_AUTO = Constants.FrontendAtsc3Fec.AUTO;
+    /** @hide */
     public static final int FRONTEND_ATSC3_FEC_BCH_LDPC_16K =
             Constants.FrontendAtsc3Fec.BCH_LDPC_16K;
+    /** @hide */
     public static final int FRONTEND_ATSC3_FEC_BCH_LDPC_64K =
             Constants.FrontendAtsc3Fec.BCH_LDPC_64K;
+    /** @hide */
     public static final int FRONTEND_ATSC3_FEC_CRC_LDPC_16K =
             Constants.FrontendAtsc3Fec.CRC_LDPC_16K;
+    /** @hide */
     public static final int FRONTEND_ATSC3_FEC_CRC_LDPC_64K =
             Constants.FrontendAtsc3Fec.CRC_LDPC_64K;
+    /** @hide */
     public static final int FRONTEND_ATSC3_FEC_LDPC_16K = Constants.FrontendAtsc3Fec.LDPC_16K;
+    /** @hide */
     public static final int FRONTEND_ATSC3_FEC_LDPC_64K = Constants.FrontendAtsc3Fec.LDPC_64K;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_UNDEFINED,
             FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET,
             FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendAtsc3DemodOutputFormat {}
-
+    /** @hide */
     public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_UNDEFINED =
             Constants.FrontendAtsc3DemodOutputFormat.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET =
             Constants.FrontendAtsc3DemodOutputFormat.ATSC3_LINKLAYER_PACKET;
+    /** @hide */
     public static final int FRONTEND_ATSC3_DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET =
             Constants.FrontendAtsc3DemodOutputFormat.BASEBAND_PACKET;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_DVBS_STANDARD_AUTO, FRONTEND_DVBS_STANDARD_S, FRONTEND_DVBS_STANDARD_S2,
             FRONTEND_DVBS_STANDARD_S2X})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendDvbsStandard {}
-
+    /** @hide */
     public static final int FRONTEND_DVBS_STANDARD_AUTO = Constants.FrontendDvbsStandard.AUTO;
+    /** @hide */
     public static final int FRONTEND_DVBS_STANDARD_S = Constants.FrontendDvbsStandard.S;
+    /** @hide */
     public static final int FRONTEND_DVBS_STANDARD_S2 = Constants.FrontendDvbsStandard.S2;
+    /** @hide */
     public static final int FRONTEND_DVBS_STANDARD_S2X = Constants.FrontendDvbsStandard.S2X;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_DVBC_ANNEX_UNDEFINED, FRONTEND_DVBC_ANNEX_A, FRONTEND_DVBC_ANNEX_B,
             FRONTEND_DVBC_ANNEX_C})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendDvbcAnnex {}
-
+    /** @hide */
     public static final int FRONTEND_DVBC_ANNEX_UNDEFINED = Constants.FrontendDvbcAnnex.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_DVBC_ANNEX_A = Constants.FrontendDvbcAnnex.A;
+    /** @hide */
     public static final int FRONTEND_DVBC_ANNEX_B = Constants.FrontendDvbcAnnex.B;
+    /** @hide */
     public static final int FRONTEND_DVBC_ANNEX_C = Constants.FrontendDvbcAnnex.C;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_DVBT_TRANSMISSION_MODE_UNDEFINED, FRONTEND_DVBT_TRANSMISSION_MODE_AUTO,
             FRONTEND_DVBT_TRANSMISSION_MODE_2K, FRONTEND_DVBT_TRANSMISSION_MODE_8K,
             FRONTEND_DVBT_TRANSMISSION_MODE_4K, FRONTEND_DVBT_TRANSMISSION_MODE_1K,
             FRONTEND_DVBT_TRANSMISSION_MODE_16K, FRONTEND_DVBT_TRANSMISSION_MODE_32K})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendDvbtTransmissionMode {}
-
+    /** @hide */
     public static final int FRONTEND_DVBT_TRANSMISSION_MODE_UNDEFINED =
             Constants.FrontendDvbtTransmissionMode.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_DVBT_TRANSMISSION_MODE_AUTO =
             Constants.FrontendDvbtTransmissionMode.AUTO;
+    /** @hide */
     public static final int FRONTEND_DVBT_TRANSMISSION_MODE_2K =
             Constants.FrontendDvbtTransmissionMode.MODE_2K;
+    /** @hide */
     public static final int FRONTEND_DVBT_TRANSMISSION_MODE_8K =
             Constants.FrontendDvbtTransmissionMode.MODE_8K;
+    /** @hide */
     public static final int FRONTEND_DVBT_TRANSMISSION_MODE_4K =
             Constants.FrontendDvbtTransmissionMode.MODE_4K;
+    /** @hide */
     public static final int FRONTEND_DVBT_TRANSMISSION_MODE_1K =
             Constants.FrontendDvbtTransmissionMode.MODE_1K;
+    /** @hide */
     public static final int FRONTEND_DVBT_TRANSMISSION_MODE_16K =
             Constants.FrontendDvbtTransmissionMode.MODE_16K;
+    /** @hide */
     public static final int FRONTEND_DVBT_TRANSMISSION_MODE_32K =
             Constants.FrontendDvbtTransmissionMode.MODE_32K;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_DVBT_BANDWIDTH_UNDEFINED, FRONTEND_DVBT_BANDWIDTH_AUTO,
             FRONTEND_DVBT_BANDWIDTH_8MHZ, FRONTEND_DVBT_BANDWIDTH_7MHZ,
             FRONTEND_DVBT_BANDWIDTH_6MHZ, FRONTEND_DVBT_BANDWIDTH_5MHZ,
             FRONTEND_DVBT_BANDWIDTH_1_7MHZ, FRONTEND_DVBT_BANDWIDTH_10MHZ})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendDvbtBandwidth {}
-
+    /** @hide */
     public static final int FRONTEND_DVBT_BANDWIDTH_UNDEFINED =
             Constants.FrontendDvbtBandwidth.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_DVBT_BANDWIDTH_AUTO = Constants.FrontendDvbtBandwidth.AUTO;
+    /** @hide */
     public static final int FRONTEND_DVBT_BANDWIDTH_8MHZ =
             Constants.FrontendDvbtBandwidth.BANDWIDTH_8MHZ;
+    /** @hide */
     public static final int FRONTEND_DVBT_BANDWIDTH_7MHZ =
             Constants.FrontendDvbtBandwidth.BANDWIDTH_7MHZ;
+    /** @hide */
     public static final int FRONTEND_DVBT_BANDWIDTH_6MHZ =
             Constants.FrontendDvbtBandwidth.BANDWIDTH_6MHZ;
+    /** @hide */
     public static final int FRONTEND_DVBT_BANDWIDTH_5MHZ =
             Constants.FrontendDvbtBandwidth.BANDWIDTH_5MHZ;
+    /** @hide */
     public static final int FRONTEND_DVBT_BANDWIDTH_1_7MHZ =
             Constants.FrontendDvbtBandwidth.BANDWIDTH_1_7MHZ;
+    /** @hide */
     public static final int FRONTEND_DVBT_BANDWIDTH_10MHZ =
             Constants.FrontendDvbtBandwidth.BANDWIDTH_10MHZ;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_DVBT_CONSTELLATION_UNDEFINED, FRONTEND_DVBT_CONSTELLATION_AUTO,
             FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_QPSK,
             FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_16QAM,
             FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_64QAM,
             FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_256QAM})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendDvbtConstellation {}
-
+    /** @hide */
     public static final int FRONTEND_DVBT_CONSTELLATION_UNDEFINED =
             Constants.FrontendDvbtConstellation.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_DVBT_CONSTELLATION_AUTO =
             Constants.FrontendDvbtConstellation.AUTO;
+    /** @hide */
     public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_QPSK =
             Constants.FrontendDvbtConstellation.CONSTELLATION_QPSK;
+    /** @hide */
     public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_16QAM =
             Constants.FrontendDvbtConstellation.CONSTELLATION_16QAM;
+    /** @hide */
     public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_64QAM =
             Constants.FrontendDvbtConstellation.CONSTELLATION_64QAM;
+    /** @hide */
     public static final int FRONTEND_DVBT_CONSTELLATION_CONSTELLATION_256QAM =
             Constants.FrontendDvbtConstellation.CONSTELLATION_256QAM;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_DVBT_CODERATE_UNDEFINED, FRONTEND_DVBT_CODERATE_AUTO,
             FRONTEND_DVBT_CODERATE_1_2, FRONTEND_DVBT_CODERATE_2_3, FRONTEND_DVBT_CODERATE_3_4,
             FRONTEND_DVBT_CODERATE_5_6, FRONTEND_DVBT_CODERATE_7_8, FRONTEND_DVBT_CODERATE_3_5,
             FRONTEND_DVBT_CODERATE_4_5, FRONTEND_DVBT_CODERATE_6_7, FRONTEND_DVBT_CODERATE_8_9})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendDvbtCoderate {}
-
+    /** @hide */
     public static final int FRONTEND_DVBT_CODERATE_UNDEFINED =
             Constants.FrontendDvbtCoderate.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_DVBT_CODERATE_AUTO = Constants.FrontendDvbtCoderate.AUTO;
+    /** @hide */
     public static final int FRONTEND_DVBT_CODERATE_1_2 =
             Constants.FrontendDvbtCoderate.CODERATE_1_2;
+    /** @hide */
     public static final int FRONTEND_DVBT_CODERATE_2_3 =
             Constants.FrontendDvbtCoderate.CODERATE_2_3;
+    /** @hide */
     public static final int FRONTEND_DVBT_CODERATE_3_4 =
             Constants.FrontendDvbtCoderate.CODERATE_3_4;
+    /** @hide */
     public static final int FRONTEND_DVBT_CODERATE_5_6 =
             Constants.FrontendDvbtCoderate.CODERATE_5_6;
+    /** @hide */
     public static final int FRONTEND_DVBT_CODERATE_7_8 =
             Constants.FrontendDvbtCoderate.CODERATE_7_8;
+    /** @hide */
     public static final int FRONTEND_DVBT_CODERATE_3_5 =
             Constants.FrontendDvbtCoderate.CODERATE_3_5;
+    /** @hide */
     public static final int FRONTEND_DVBT_CODERATE_4_5 =
             Constants.FrontendDvbtCoderate.CODERATE_4_5;
+    /** @hide */
     public static final int FRONTEND_DVBT_CODERATE_6_7 =
             Constants.FrontendDvbtCoderate.CODERATE_6_7;
+    /** @hide */
     public static final int FRONTEND_DVBT_CODERATE_8_9 =
             Constants.FrontendDvbtCoderate.CODERATE_8_9;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_DVBT_GUARD_INTERVAL_UNDEFINED, FRONTEND_DVBT_GUARD_INTERVAL_AUTO,
             FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_32, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_16,
             FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_8, FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_4,
             FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_128,
             FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_128,
             FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_256})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendDvbtGuardInterval {}
-
+    /** @hide */
     public static final int FRONTEND_DVBT_GUARD_INTERVAL_UNDEFINED =
             Constants.FrontendDvbtGuardInterval.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_DVBT_GUARD_INTERVAL_AUTO =
             Constants.FrontendDvbtGuardInterval.AUTO;
+    /** @hide */
     public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_32 =
             Constants.FrontendDvbtGuardInterval.INTERVAL_1_32;
+    /** @hide */
     public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_16 =
             Constants.FrontendDvbtGuardInterval.INTERVAL_1_16;
+    /** @hide */
     public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_8 =
             Constants.FrontendDvbtGuardInterval.INTERVAL_1_8;
+    /** @hide */
     public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_4 =
             Constants.FrontendDvbtGuardInterval.INTERVAL_1_4;
+    /** @hide */
     public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_1_128 =
             Constants.FrontendDvbtGuardInterval.INTERVAL_1_128;
+    /** @hide */
     public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_128 =
             Constants.FrontendDvbtGuardInterval.INTERVAL_19_128;
+    /** @hide */
     public static final int FRONTEND_DVBT_GUARD_INTERVAL_INTERVAL_19_256 =
             Constants.FrontendDvbtGuardInterval.INTERVAL_19_256;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_ISDBS_CODERATE_UNDEFINED, FRONTEND_ISDBS_CODERATE_AUTO,
             FRONTEND_ISDBS_CODERATE_1_2, FRONTEND_ISDBS_CODERATE_2_3, FRONTEND_ISDBS_CODERATE_3_4,
             FRONTEND_ISDBS_CODERATE_5_6, FRONTEND_ISDBS_CODERATE_7_8})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendIsdbsCoderate {}
-
+    /** @hide */
     public static final int FRONTEND_ISDBS_CODERATE_UNDEFINED =
             Constants.FrontendIsdbsCoderate.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_ISDBS_CODERATE_AUTO = Constants.FrontendIsdbsCoderate.AUTO;
+    /** @hide */
     public static final int FRONTEND_ISDBS_CODERATE_1_2 =
             Constants.FrontendIsdbsCoderate.CODERATE_1_2;
+    /** @hide */
     public static final int FRONTEND_ISDBS_CODERATE_2_3 =
             Constants.FrontendIsdbsCoderate.CODERATE_2_3;
+    /** @hide */
     public static final int FRONTEND_ISDBS_CODERATE_3_4 =
             Constants.FrontendIsdbsCoderate.CODERATE_3_4;
+    /** @hide */
     public static final int FRONTEND_ISDBS_CODERATE_5_6 =
             Constants.FrontendIsdbsCoderate.CODERATE_5_6;
+    /** @hide */
     public static final int FRONTEND_ISDBS_CODERATE_7_8 =
             Constants.FrontendIsdbsCoderate.CODERATE_7_8;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_ISDBT_MODE_UNDEFINED, FRONTEND_ISDBT_MODE_AUTO, FRONTEND_ISDBT_MODE_1,
             FRONTEND_ISDBT_MODE_2, FRONTEND_ISDBT_MODE_3})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendIsdbtMode {}
-
+    /** @hide */
     public static final int FRONTEND_ISDBT_MODE_UNDEFINED = Constants.FrontendIsdbtMode.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_ISDBT_MODE_AUTO = Constants.FrontendIsdbtMode.AUTO;
+    /** @hide */
     public static final int FRONTEND_ISDBT_MODE_1 = Constants.FrontendIsdbtMode.MODE_1;
+    /** @hide */
     public static final int FRONTEND_ISDBT_MODE_2 = Constants.FrontendIsdbtMode.MODE_2;
+    /** @hide */
     public static final int FRONTEND_ISDBT_MODE_3 = Constants.FrontendIsdbtMode.MODE_3;
 
-    @Retention(RetentionPolicy.SOURCE)
+    /** @hide */
     @IntDef({FRONTEND_ISDBT_BANDWIDTH_UNDEFINED, FRONTEND_ISDBT_BANDWIDTH_AUTO,
             FRONTEND_ISDBT_BANDWIDTH_8MHZ, FRONTEND_ISDBT_BANDWIDTH_7MHZ,
             FRONTEND_ISDBT_BANDWIDTH_6MHZ})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface FrontendIsdbtBandwidth {}
-
+    /** @hide */
     public static final int FRONTEND_ISDBT_BANDWIDTH_UNDEFINED =
             Constants.FrontendIsdbtBandwidth.UNDEFINED;
+    /** @hide */
     public static final int FRONTEND_ISDBT_BANDWIDTH_AUTO = Constants.FrontendIsdbtBandwidth.AUTO;
+    /** @hide */
     public static final int FRONTEND_ISDBT_BANDWIDTH_8MHZ =
             Constants.FrontendIsdbtBandwidth.BANDWIDTH_8MHZ;
+    /** @hide */
     public static final int FRONTEND_ISDBT_BANDWIDTH_7MHZ =
             Constants.FrontendIsdbtBandwidth.BANDWIDTH_7MHZ;
+    /** @hide */
     public static final int FRONTEND_ISDBT_BANDWIDTH_6MHZ =
             Constants.FrontendIsdbtBandwidth.BANDWIDTH_6MHZ;
 
diff --git a/media/java/android/media/tv/tuner/filter/FilterEvent.java b/media/java/android/media/tv/tuner/filter/FilterEvent.java
index 0a4fa86..56a77d4 100644
--- a/media/java/android/media/tv/tuner/filter/FilterEvent.java
+++ b/media/java/android/media/tv/tuner/filter/FilterEvent.java
@@ -16,10 +16,13 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.SystemApi;
+
 /**
- * Demux filter event.
+ * An entity class that is passed to the filter callbacks.
  *
  * @hide
  */
+@SystemApi
 public abstract class FilterEvent {
 }
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
index 5e45350..7703248 100644
--- a/media/java/android/media/tv/tuner/filter/MediaEvent.java
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -22,7 +22,7 @@
  * Media event.
  * @hide
  */
-public class MediaEvent {
+public class MediaEvent extends FilterEvent {
     private int mStreamId;
     private boolean mIsPtsPresent;
     private long mPts;
diff --git a/media/java/android/media/tv/tuner/filter/SectionEvent.java b/media/java/android/media/tv/tuner/filter/SectionEvent.java
index 5b5f8c9..e211dda 100644
--- a/media/java/android/media/tv/tuner/filter/SectionEvent.java
+++ b/media/java/android/media/tv/tuner/filter/SectionEvent.java
@@ -16,14 +16,54 @@
 
 package android.media.tv.tuner.filter;
 
+import android.annotation.SystemApi;
+import android.media.tv.tuner.Tuner.Filter;
+
 /**
- * Section event.
+ * Filter event sent from {@link Filter} objects with section type.
+ *
  * @hide
  */
-public class SectionEvent {
-    // TODO: add constructor and getters
-    private int mTableId;
-    private int mVersion;
-    private int mSectionNum;
-    private int mDataLength;
+@SystemApi
+public class SectionEvent extends FilterEvent {
+    private final int mTableId;
+    private final int mVersion;
+    private final int mSectionNum;
+    private final int mDataLength;
+
+    // This constructor is used by JNI code only
+    private SectionEvent(int tableId, int version, int sectionNum, int dataLength) {
+        mTableId = tableId;
+        mVersion = version;
+        mSectionNum = sectionNum;
+        mDataLength = dataLength;
+    }
+
+    /**
+     * Gets table ID of filtered data.
+     */
+    public int getTableId() {
+        return mTableId;
+    }
+
+    /**
+     * Gets version number of filtered data.
+     */
+    public int getVersion() {
+        return mVersion;
+    }
+
+    /**
+     * Gets section number of filtered data.
+     */
+    public int getSectionNumber() {
+        return mSectionNum;
+    }
+
+    /**
+     * Gets data size in bytes of filtered data.
+     */
+    public int getDataLength() {
+        return mDataLength;
+    }
 }
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
new file mode 100644
index 0000000..16308ce
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2019 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.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for analog.
+ * @hide
+ */
+public class AnalogFrontendSettings extends FrontendSettings {
+    private int mAnalogType;
+    private int mSifStandard;
+
+    @Override
+    public int getType() {
+        return TunerConstants.FRONTEND_TYPE_ANALOG;
+    }
+
+    public int getAnalogType() {
+        return mAnalogType;
+    }
+
+    public int getSifStandard() {
+        return mSifStandard;
+    }
+
+    /**
+     * Creates a new builder object.
+     */
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    private AnalogFrontendSettings(int frequency, int analogType, int sifStandard) {
+        super(frequency);
+        mAnalogType = analogType;
+        mSifStandard = sifStandard;
+    }
+
+    /**
+     * Builder for FrontendAnalogSettings.
+     */
+    public static class Builder {
+        private int mFrequency;
+        private int mAnalogType;
+        private int mSifStandard;
+
+        private Builder() {}
+
+        /**
+         * Sets frequency.
+         */
+        public Builder setFrequency(int frequency) {
+            mFrequency = frequency;
+            return this;
+        }
+
+        /**
+         * Sets analog type.
+         */
+        public Builder setAnalogType(int analogType) {
+            mAnalogType = analogType;
+            return this;
+        }
+
+        /**
+         * Sets sif standard.
+         */
+        public Builder setSifStandard(int sifStandard) {
+            mSifStandard = sifStandard;
+            return this;
+        }
+
+        /**
+         * Builds a FrontendAnalogSettings instance.
+         */
+        public AnalogFrontendSettings build() {
+            return new AnalogFrontendSettings(mFrequency, mAnalogType, mSifStandard);
+        }
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
new file mode 100644
index 0000000..bce8a64
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 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.media.tv.tuner.frontend;
+
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+import java.util.List;
+
+/**
+ * Frontend settings for ATSC-3.
+ * @hide
+ */
+public class Atsc3FrontendSettings extends FrontendSettings {
+    public int bandwidth;
+    public byte demodOutputFormat;
+    public List<Atsc3PlpSettings> plpSettings;
+
+    Atsc3FrontendSettings(int frequency) {
+        super(frequency);
+    }
+
+    @Override
+    public int getType() {
+        return TunerConstants.FRONTEND_TYPE_ATSC3;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java b/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java
similarity index 61%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
copy to media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java
index 155a6d2..61c6fec 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright 2019 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,17 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dagger.qualifiers;
+package android.media.tv.tuner.frontend;
 
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Qualifier;
-
-@Qualifier
-@Documented
-@Retention(RUNTIME)
-public @interface DisplayId {
+/**
+ * PLP settings for ATSC-3.
+ * @hide
+ */
+public class Atsc3PlpSettings {
+    public byte plpId;
+    public int modulation;
+    public int interleaveMode;
+    public int codeRate;
+    public int fec;
 }
diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
new file mode 100644
index 0000000..14c5cdd
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 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.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for ATSC.
+ * @hide
+ */
+public class AtscFrontendSettings extends FrontendSettings {
+    public int modulation;
+
+    AtscFrontendSettings(int frequency) {
+        super(frequency);
+    }
+
+    @Override
+    public int getType() {
+        return TunerConstants.FRONTEND_TYPE_ATSC;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
new file mode 100644
index 0000000..07e49ff
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 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.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for DVBC.
+ * @hide
+ */
+public class DvbcFrontendSettings extends FrontendSettings {
+    public int modulation;
+    public long fec;
+    public int symbolRate;
+    public int outerFec;
+    public byte annex;
+    public int spectralInversion;
+
+    DvbcFrontendSettings(int frequency) {
+        super(frequency);
+    }
+
+    @Override
+    public int getType() {
+        return TunerConstants.FRONTEND_TYPE_DVBC;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java b/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java
similarity index 61%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
copy to media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java
index 155a6d2..bfa4391 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
+++ b/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright 2019 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,17 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dagger.qualifiers;
+package android.media.tv.tuner.frontend;
 
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Qualifier;
-
-@Qualifier
-@Documented
-@Retention(RUNTIME)
-public @interface DisplayId {
+/**
+ * Code rate for DVBS.
+ * @hide
+ */
+public class DvbsCodeRate {
+    public long fec;
+    public boolean isLinear;
+    public boolean isShortFrames;
+    public int bitsPer1000Symbol;
 }
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
new file mode 100644
index 0000000..23c0a7b1
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019 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.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for DVBS.
+ * @hide
+ */
+public class DvbsFrontendSettings extends FrontendSettings {
+    public int modulation;
+    public DvbsCodeRate coderate;
+    public int symbolRate;
+    public int rolloff;
+    public int pilot;
+    public int inputStreamId;
+    public byte standard;
+
+    DvbsFrontendSettings(int frequency) {
+        super(frequency);
+    }
+
+    @Override
+    public int getType() {
+        return TunerConstants.FRONTEND_TYPE_DVBS;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
new file mode 100644
index 0000000..eec00f3
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 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.media.tv.tuner.frontend;
+
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for DVBT.
+ * @hide
+ */
+public class DvbtFrontendSettings extends FrontendSettings {
+    public int transmissionMode;
+    public int bandwidth;
+    public int constellation;
+    public int hierarchy;
+    public int hpCoderate;
+    public int lpCoderate;
+    public int guardInterval;
+    public boolean isHighPriority;
+    public byte standard;
+    public boolean isMiso;
+    public int plpMode;
+    public byte plpId;
+    public byte plpGroupId;
+
+    DvbtFrontendSettings(int frequency) {
+        super(frequency);
+    }
+
+    @Override
+    public int getType() {
+        return TunerConstants.FRONTEND_TYPE_DVBT;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendCallback.java b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java
new file mode 100644
index 0000000..91776e1
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020 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.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.ScanMessage;
+
+/**
+ * Frontend Callback.
+ *
+ * @hide
+ */
+public interface FrontendCallback {
+
+    /**
+     * Invoked when there is a frontend event.
+     */
+    void onEvent(int frontendEventType);
+
+    /**
+     * Invoked when there is a scan message.
+     * @param msg
+     */
+    void onScanMessage(ScanMessage msg);
+}
diff --git a/media/java/android/media/tv/tuner/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
similarity index 96%
rename from media/java/android/media/tv/tuner/FrontendInfo.java
rename to media/java/android/media/tv/tuner/frontend/FrontendInfo.java
index 2ab100d..ef6c029 100644
--- a/media/java/android/media/tv/tuner/FrontendInfo.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package android.media.tv.tuner;
+package android.media.tv.tuner.frontend;
 
+import android.media.tv.tuner.FrontendCapabilities;
 import android.media.tv.tuner.TunerConstants.FrontendType;
 
 /**
diff --git a/media/java/android/media/tv/tuner/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
similarity index 98%
rename from media/java/android/media/tv/tuner/FrontendStatus.java
rename to media/java/android/media/tv/tuner/frontend/FrontendStatus.java
index f8b2d12..89ec536 100644
--- a/media/java/android/media/tv/tuner/FrontendStatus.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -14,8 +14,9 @@
  * limitations under the License.
  */
 
-package android.media.tv.tuner;
+package android.media.tv.tuner.frontend;
 
+import android.media.tv.tuner.TunerConstants;
 import android.media.tv.tuner.TunerConstants.FrontendDvbcSpectralInversion;
 import android.media.tv.tuner.TunerConstants.FrontendDvbtHierarchy;
 import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
new file mode 100644
index 0000000..736d0b1
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 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.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for ISDBS-3.
+ * @hide
+ */
+public class Isdbs3FrontendSettings extends FrontendSettings {
+    public int streamId;
+    public int streamIdType;
+    public int modulation;
+    public int coderate;
+    public int symbolRate;
+    public int rolloff;
+
+    Isdbs3FrontendSettings(int frequency) {
+        super(frequency);
+    }
+
+    @Override
+    public int getType() {
+        return TunerConstants.FRONTEND_TYPE_ISDBS3;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
new file mode 100644
index 0000000..7fd5da7
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 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.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for ISDBS.
+ * @hide
+ */
+public class IsdbsFrontendSettings extends FrontendSettings {
+    public int streamId;
+    public int streamIdType;
+    public int modulation;
+    public int coderate;
+    public int symbolRate;
+    public int rolloff;
+
+    IsdbsFrontendSettings(int frequency) {
+        super(frequency);
+    }
+
+    @Override
+    public int getType() {
+        return TunerConstants.FRONTEND_TYPE_ISDBS;
+    }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
new file mode 100644
index 0000000..3f83267
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 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.media.tv.tuner.frontend;
+
+
+import android.media.tv.tuner.FrontendSettings;
+import android.media.tv.tuner.TunerConstants;
+
+/**
+ * Frontend settings for ISDBT.
+ * @hide
+ */
+public class IsdbtFrontendSettings extends FrontendSettings {
+    public int modulation;
+    public int bandwidth;
+    public int coderate;
+    public int guardInterval;
+    public int serviceAreaId;
+
+    IsdbtFrontendSettings(int frequency) {
+        super(frequency);
+    }
+
+    @Override
+    public int getType() {
+        return TunerConstants.FRONTEND_TYPE_ISDBT;
+    }
+}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 3562782..f0f3688 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -100,7 +100,7 @@
 
 /////////////// Dvr ///////////////////////
 
-Dvr::Dvr(sp<IDvr> sp, jweak obj) : mDvrSp(sp), mDvrObj(obj) {}
+Dvr::Dvr(sp<IDvr> sp, jweak obj) : mDvrSp(sp), mDvrObj(obj), mDvrMQEventFlag(nullptr) {}
 
 Dvr::~Dvr() {
     EventFlag::deleteEventFlag(&mDvrMQEventFlag);
@@ -880,6 +880,10 @@
     return tuner->openDvr(static_cast<DvrType>(type), bufferSize);
 }
 
+static jobject android_media_tv_Tuner_get_demux_caps(JNIEnv*, jobject) {
+    return NULL;
+}
+
 static int android_media_tv_Tuner_attach_filter(JNIEnv *env, jobject dvr, jobject filter) {
     sp<IDvr> dvrSp = getDvr(env, dvr)->getIDvr();
     sp<IFilter> filterSp = getFilter(env, filter)->getIFilter();
@@ -1123,6 +1127,8 @@
             (void *)android_media_tv_Tuner_open_descrambler },
     { "nativeOpenDvr", "(II)Landroid/media/tv/tuner/Tuner$Dvr;",
             (void *)android_media_tv_Tuner_open_dvr },
+    { "nativeGetDemuxCapabilities", "()Landroid/media/tv/tuner/DemuxCapabilities;",
+            (void *)android_media_tv_Tuner_get_demux_caps },
 };
 
 static const JNINativeMethod gFilterMethods[] = {
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index 6fe847b..86b9706 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -701,7 +701,6 @@
     static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
         Map<String, MediaRoute2Info> routeMap = new HashMap<>();
         for (MediaRoute2Info route : routes) {
-            // intentionally not using route.getUniqueId() for convenience.
             routeMap.put(route.getId(), route);
         }
         return routeMap;
@@ -741,7 +740,7 @@
     }
 
     /**
-     * Returns a list of IDs (not uniqueId) of the given route list.
+     * Returns a list of IDs of the given route list.
      */
     List<String> getRouteIds(@NonNull List<MediaRoute2Info> routes) {
         List<String> result = new ArrayList<>();
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index 83c7c17..1fd0141 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -57,30 +57,37 @@
 public class MediaRouterManagerTest {
     private static final String TAG = "MediaRouterManagerTest";
 
-    // Must be the same as SampleMediaRoute2ProviderService
-    public static final String ROUTE_ID1 = "route_id1";
+    public static final String SAMPLE_PROVIDER_ROUTES_ID_PREFIX =
+            "com.android.mediarouteprovider.example/.SampleMediaRoute2ProviderService:";
+
+    // Must be the same as SampleMediaRoute2ProviderService except the prefix of IDs.
+    public static final String ROUTE_ID1 = SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id1";
     public static final String ROUTE_NAME1 = "Sample Route 1";
-    public static final String ROUTE_ID2 = "route_id2";
+    public static final String ROUTE_ID2 = SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id2";
     public static final String ROUTE_NAME2 = "Sample Route 2";
     public static final String ROUTE_ID3_SESSION_CREATION_FAILED =
-            "route_id3_session_creation_failed";
+            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id3_session_creation_failed";
     public static final String ROUTE_NAME3 = "Sample Route 3 - Session creation failed";
     public static final String ROUTE_ID4_TO_SELECT_AND_DESELECT =
-            "route_id4_to_select_and_deselect";
+            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id4_to_select_and_deselect";
     public static final String ROUTE_NAME4 = "Sample Route 4 - Route to select and deselect";
-    public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to";
+    public static final String ROUTE_ID5_TO_TRANSFER_TO =
+            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id5_to_transfer_to";
     public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
 
-    public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
+    public static final String ROUTE_ID_SPECIAL_CATEGORY =
+            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_category";
     public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
 
     public static final String SYSTEM_PROVIDER_ID =
             "com.android.server.media/.SystemMediaRoute2Provider";
 
     public static final int VOLUME_MAX = 100;
-    public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
+    public static final String ROUTE_ID_FIXED_VOLUME =
+            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_fixed_volume";
     public static final String ROUTE_NAME_FIXED_VOLUME = "Fixed Volume Route";
-    public static final String ROUTE_ID_VARIABLE_VOLUME = "route_variable_volume";
+    public static final String ROUTE_ID_VARIABLE_VOLUME =
+            SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_variable_volume";
     public static final String ROUTE_NAME_VARIABLE_VOLUME = "Variable Volume Route";
 
     public static final String ACTION_REMOVE_ROUTE =
@@ -430,7 +437,6 @@
     static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
         Map<String, MediaRoute2Info> routeMap = new HashMap<>();
         for (MediaRoute2Info route : routes) {
-            // intentionally not using route.getUniqueId() for convenience.
             routeMap.put(route.getId(), route);
         }
         return routeMap;
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml
deleted file mode 100644
index dd22545..0000000
--- a/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 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:viewportWidth="44"
-        android:viewportHeight="44"
-        android:width="44dp"
-        android:height="44dp">
-    <path
-        android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z"
-        android:fillColor="@color/car_nav_icon_fill_color_selected" />
-</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml
deleted file mode 100644
index c5d7728..0000000
--- a/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2019 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:viewportWidth="44"
-        android:viewportHeight="44"
-        android:width="44dp"
-        android:height="44dp">
-  <path
-      android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z"
-      android:fillColor="@color/car_nav_icon_fill_color_selected" />
-  <group
-      android:translateX="30"
-      android:translateY="2">
-    <path
-        android:fillColor="@color/car_nav_notification_unseen_indicator_color"
-        android:strokeWidth="1"
-        android:pathData="M 6 0 C 9.31370849898 0 12 2.68629150102 12 6 C 12 9.31370849898 9.31370849898 12 6 12 C 2.68629150102 12 0 9.31370849898 0 6 C 0 2.68629150102 2.68629150102 0 6 0 Z" />
-  </group>
-</vector>
-
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml b/packages/CarSystemUI/res/drawable/car_ic_unseen_indicator.xml
similarity index 69%
rename from packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml
rename to packages/CarSystemUI/res/drawable/car_ic_unseen_indicator.xml
index 25d1af3..025fc9c 100644
--- a/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml
+++ b/packages/CarSystemUI/res/drawable/car_ic_unseen_indicator.xml
@@ -19,14 +19,11 @@
         android:viewportHeight="44"
         android:width="44dp"
         android:height="44dp">
-  <path
-      android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z"
-      android:fillColor="@color/car_nav_icon_fill_color" />
   <group
       android:translateX="30"
       android:translateY="2">
     <path
-        android:fillColor="@color/car_nav_notification_unseen_indicator_color"
+        android:fillColor="@color/car_nav_unseen_indicator_color"
         android:strokeWidth="1"
         android:pathData="M 6 0 C 9.31370849898 0 12 2.68629150102 12 6 C 12 9.31370849898 9.31370849898 12 6 12 C 2.68629150102 12 0 9.31370849898 0 6 C 0 2.68629150102 2.68629150102 0 6 0 Z" />
   </group>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index 27ad4fc..e2e9a33 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -114,7 +114,6 @@
             style="@style/NavigationBarButton"
             systemui:icon="@drawable/car_ic_notification"
             systemui:longIntent="intent:#Intent;component=com.android.car.bugreport/.BugReportActivity;end"
-            systemui:selectedIcon="@drawable/car_ic_notification_selected"
         />
 
         <Space
@@ -138,8 +137,7 @@
         android:paddingStart="@dimen/car_keyline_1"
         android:paddingEnd="@dimen/car_keyline_1"
         android:gravity="center"
-        android:visibility="gone">
-
-    </LinearLayout>
+        android:visibility="gone"
+    />
 
 </com.android.systemui.navigationbar.car.CarNavigationBarView>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/layout/car_navigation_button.xml b/packages/CarSystemUI/res/layout/car_navigation_button.xml
index bafbb64..837252b 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_button.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_button.xml
@@ -18,13 +18,12 @@
 -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
-    <LinearLayout
+    <FrameLayout
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/car_nav_button_icon"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:layout_weight="1"
-        android:gravity="center"
         android:animateLayoutChanges="true"
         android:orientation="vertical">
 
@@ -32,20 +31,35 @@
             android:id="@+id/car_nav_button_icon_image"
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
+            android:layout_gravity="center"
             android:animateLayoutChanges="true"
             android:background="@android:color/transparent"
-            android:scaleType="fitCenter">
-        </com.android.keyguard.AlphaOptimizedImageButton>
+            android:scaleType="fitCenter"
+            android:clickable="false"
+        />
 
         <com.android.keyguard.AlphaOptimizedImageButton
             android:id="@+id/car_nav_button_more_icon"
             android:layout_height="wrap_content"
             android:layout_width="match_parent"
+            android:layout_gravity="center"
             android:animateLayoutChanges="true"
             android:src="@drawable/car_ic_arrow"
             android:background="@android:color/transparent"
-            android:scaleType="fitCenter">
-        </com.android.keyguard.AlphaOptimizedImageButton>
+            android:scaleType="fitCenter"
+            android:clickable="false"
+        />
 
-    </LinearLayout>
+        <ImageView
+            android:id="@+id/car_nav_button_unseen_icon"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:layout_gravity="center"
+            android:src="@drawable/car_ic_unseen_indicator"
+            android:background="@android:color/transparent"
+            android:scaleType="fitCenter"
+            android:clickable="false"
+        />
+
+    </FrameLayout>
 </merge>
diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml
index 0b34626..37cd1d4 100644
--- a/packages/CarSystemUI/res/layout/super_status_bar.xml
+++ b/packages/CarSystemUI/res/layout/super_status_bar.xml
@@ -93,7 +93,7 @@
         android:layout_height="match_parent"
         android:visibility="invisible"/>
 
-    <include layout="@layout/status_bar_expanded"
+    <ViewStub android:id="@+id/status_bar_expanded"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:visibility="invisible"/>
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
index 5fcf38fc..7972e09 100644
--- a/packages/CarSystemUI/res/values/colors.xml
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -43,8 +43,8 @@
     <!-- The color of the dividing line between grouped notifications. -->
     <color name="notification_divider_color">@*android:color/notification_action_list</color>
 
-    <!-- The color for the unseen notification indicator. -->
-    <color name="car_nav_notification_unseen_indicator_color">#e25142</color>
+    <!-- The color for the unseen indicator. -->
+    <color name="car_nav_unseen_indicator_color">#e25142</color>
 
     <!-- The color of the ripples on the untinted notifications -->
     <color name="notification_ripple_untinted_color">@color/ripple_material_light</color>
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
index ed945e7..f7802d2 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
@@ -18,6 +18,7 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
@@ -40,8 +41,9 @@
             NotifLog notifLog,
             NotificationGroupManager groupManager,
             NotificationRankingManager rankingManager,
-            KeyguardEnvironment keyguardEnvironment) {
-        super(notifLog, groupManager, rankingManager, keyguardEnvironment);
+            KeyguardEnvironment keyguardEnvironment,
+            FeatureFlags featureFlags) {
+        super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags);
     }
 
     @Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java
index d08fbbc..b4d4785 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java
@@ -48,13 +48,11 @@
     private static final String BUTTON_FILTER_DELIMITER = ";";
     private static final String EXTRA_BUTTON_CATEGORIES = "categories";
     private static final String EXTRA_BUTTON_PACKAGES = "packages";
-    private static final int UNSEEN_ICON_RESOURCE_ID = R.drawable.car_ic_notification_unseen;
-    private static final int UNSEEN_SELECTED_ICON_RESOURCE_ID =
-            R.drawable.car_ic_notification_selected_unseen;
 
     private Context mContext;
     private AlphaOptimizedImageButton mIcon;
     private AlphaOptimizedImageButton mMoreIcon;
+    private ImageView mUnseenIcon;
     private String mIntent;
     private String mLongIntent;
     private boolean mBroadcastIntent;
@@ -265,23 +263,22 @@
 
         mIcon = findViewById(R.id.car_nav_button_icon_image);
         mIcon.setScaleType(ImageView.ScaleType.CENTER);
-        mIcon.setClickable(false);
         // Always apply selected alpha if the button does not toggle alpha based on selection state.
         mIcon.setAlpha(mHighlightWhenSelected ? mUnselectedAlpha : mSelectedAlpha);
         mIcon.setImageResource(mIconResourceId);
 
         mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
-        mMoreIcon.setClickable(false);
         mMoreIcon.setAlpha(mSelectedAlpha);
         mMoreIcon.setVisibility(GONE);
+
+        mUnseenIcon = findViewById(R.id.car_nav_button_unseen_icon);
+
+        mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE);
     }
 
     private void updateImage() {
-        if (mHasUnseen) {
-            mIcon.setImageResource(mSelected ? UNSEEN_SELECTED_ICON_RESOURCE_ID
-                    : UNSEEN_ICON_RESOURCE_ID);
-        } else {
-            mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
-        }
+        mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
+        mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE);
     }
+
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 3c3ebe2..77db54c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -128,11 +128,11 @@
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.phone.StatusBarComponent;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
-import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -465,7 +465,7 @@
 
         super.start();
 
-        mNotificationPanelViewController.setScrollingEnabled(true);
+        mNotificationPanel.setScrollingEnabled(true);
         mSettleOpenPercentage = mContext.getResources().getInteger(
                 R.integer.notification_settle_open_percentage);
         mSettleClosePercentage = mContext.getResources().getInteger(
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index a1eccce..1ebaef7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -86,11 +86,11 @@
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBarComponent;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
-import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationButtonTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationButtonTest.java
index e1ee616..96d567d 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationButtonTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationButtonTest.java
@@ -31,6 +31,7 @@
 import android.testing.TestableLooper;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 
 import androidx.test.filters.SmallTest;
@@ -216,6 +217,22 @@
         }), any());
     }
 
+    @Test
+    public void onSetUnseen_hasUnseen_showsUnseenIndicator() {
+        mDefaultButton.setUnseen(true);
+        ImageView hasUnseenIndicator = mDefaultButton.findViewById(R.id.car_nav_button_unseen_icon);
+
+        assertThat(hasUnseenIndicator.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void onSetUnseen_doesNotHaveUnseen_hidesUnseenIndicator() {
+        mDefaultButton.setUnseen(false);
+        ImageView hasUnseenIndicator = mDefaultButton.findViewById(R.id.car_nav_button_unseen_icon);
+
+        assertThat(hasUnseenIndicator.getVisibility()).isEqualTo(View.GONE);
+    }
+
     private String getCurrentActivityName() {
         return mActivityManager.getRunningTasks(1).get(0).topActivity.flattenToShortString();
     }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 9e49826..c2ce840 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -262,10 +262,11 @@
             return;
         }
 
+        stopForeground(true);
         mJustCancelledByUser = true;
 
         if (mInstallTask.cancel(false)) {
-            // Will cleanup and post status in onResult()
+            // Will stopSelf() in onResult()
             Log.d(TAG, "Cancel request filed successfully");
         } else {
             Log.e(TAG, "Trying to cancel installation while it's already completed.");
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index f7e9fed..4d184d5 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -240,6 +240,15 @@
     <!-- Description of airplane mode -->
     <string name="airplane_mode">Airplane mode</string>
 
+    <!-- An explanation text that the PIN needs to be entered to prepare for an operating system update. [CHAR LIMIT=80] -->
+    <string name="kg_prompt_reason_prepare_for_update_pin">PIN required to prepare for update</string>
+
+    <!-- An explanation text that the pattern needs to be entered to prepare for an operating system update. [CHAR LIMIT=80] -->
+    <string name="kg_prompt_reason_prepare_for_update_pattern">Pattern required to prepare for update</string>
+
+    <!-- An explanation text that the password needs to be entered to prepare for an operating system update. [CHAR LIMIT=80] -->
+    <string name="kg_prompt_reason_prepare_for_update_password">Password required to prepare for update</string>
+
     <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=80] -->
     <string name="kg_prompt_reason_restart_pattern">Pattern required after device restarts</string>
 
diff --git a/packages/SystemUI/res/layout/people_strip.xml b/packages/SystemUI/res/layout/people_strip.xml
index f0ac08b..982aa8e 100644
--- a/packages/SystemUI/res/layout/people_strip.xml
+++ b/packages/SystemUI/res/layout/people_strip.xml
@@ -18,7 +18,10 @@
 <com.android.systemui.statusbar.notification.stack.PeopleHubView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="105dp">
+    android:layout_height="@dimen/notification_section_header_height"
+    android:focusable="true"
+    android:clickable="true"
+>
 
     <com.android.systemui.statusbar.notification.row.NotificationBackgroundView
         android:id="@+id/backgroundNormal"
@@ -34,199 +37,56 @@
         android:id="@+id/people_list"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:paddingTop="12dp"
-        android:paddingBottom="12dp"
         android:gravity="center"
         android:orientation="horizontal">
 
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
             android:layout_weight="1"
+            android:layout_marginStart="@dimen/notification_section_header_padding_left"
+            android:gravity="start"
+            android:textAlignment="gravity"
+            android:text="@string/notification_section_header_conversations"
+            android:textSize="12sp"
+            android:textColor="@color/notification_section_header_label_color"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
         />
 
-        <LinearLayout
-            android:layout_width="70dp"
-            android:layout_height="match_parent"
-            android:gravity="center_horizontal"
-            android:orientation="vertical"
-            android:visibility="invisible">
-
-            <ImageView
-                android:id="@+id/person_icon"
-                android:layout_width="36dp"
-                android:layout_height="36dp"
-                android:scaleType="fitCenter"
-            />
-
-            <TextView
-                android:id="@+id/person_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingTop="8dp"
-                android:ellipsize="end"
-                android:maxLines="2"
-                android:textAlignment="center"
-            />
-
-        </LinearLayout>
-
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
+        <ImageView
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:scaleType="fitCenter"
         />
 
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
+        <ImageView
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:scaleType="fitCenter"
         />
 
-        <LinearLayout
-            android:layout_width="70dp"
-            android:layout_height="match_parent"
-            android:gravity="center_horizontal"
-            android:orientation="vertical"
-            android:visibility="invisible">
-
-            <ImageView
-                android:id="@+id/person_icon"
-                android:layout_width="36dp"
-                android:layout_height="36dp"
-                android:scaleType="fitCenter"
-            />
-
-            <TextView
-                android:id="@+id/person_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingTop="8dp"
-                android:ellipsize="end"
-                android:maxLines="2"
-                android:textAlignment="center"
-            />
-
-        </LinearLayout>
-
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
+        <ImageView
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:scaleType="fitCenter"
         />
 
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
+        <ImageView
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:padding="8dp"
+            android:scaleType="fitCenter"
         />
 
-        <LinearLayout
-            android:layout_width="70dp"
-            android:layout_height="match_parent"
-            android:gravity="center_horizontal"
-            android:orientation="vertical"
-            android:visibility="invisible">
-
-            <ImageView
-                android:id="@+id/person_icon"
-                android:layout_width="36dp"
-                android:layout_height="36dp"
-                android:scaleType="fitCenter"
-            />
-
-            <TextView
-                android:id="@+id/person_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingTop="8dp"
-                android:ellipsize="end"
-                android:maxLines="2"
-                android:textAlignment="center"
-            />
-
-        </LinearLayout>
-
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-        />
-
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-        />
-
-        <LinearLayout
-            android:layout_width="70dp"
-            android:layout_height="match_parent"
-            android:gravity="center_horizontal"
-            android:orientation="vertical"
-            android:visibility="invisible">
-
-            <ImageView
-                android:id="@+id/person_icon"
-                android:layout_width="36dp"
-                android:layout_height="36dp"
-                android:scaleType="fitCenter"
-            />
-
-            <TextView
-                android:id="@+id/person_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingTop="8dp"
-                android:ellipsize="end"
-                android:maxLines="2"
-                android:textAlignment="center"
-            />
-
-        </LinearLayout>
-
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-        />
-
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-        />
-
-        <LinearLayout
-            android:layout_width="70dp"
-            android:layout_height="match_parent"
-            android:gravity="center_horizontal"
-            android:orientation="vertical"
-            android:visibility="invisible">
-
-            <ImageView
-                android:id="@+id/person_icon"
-                android:layout_width="36dp"
-                android:layout_height="36dp"
-                android:scaleType="fitCenter"
-            />
-
-            <TextView
-                android:id="@+id/person_name"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:paddingTop="8dp"
-                android:ellipsize="end"
-                android:maxLines="2"
-                android:textAlignment="center"
-            />
-
-        </LinearLayout>
-
-        <View
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
+        <ImageView
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:layout_marginEnd="8dp"
+            android:padding="8dp"
+            android:scaleType="fitCenter"
         />
 
     </LinearLayout>
@@ -236,4 +96,4 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
-</com.android.systemui.statusbar.notification.stack.PeopleHubView>
\ No newline at end of file
+</com.android.systemui.statusbar.notification.stack.PeopleHubView>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 479f255..4869be1 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -17,14 +17,9 @@
 */
 -->
 
-
-<com.android.systemui.statusbar.phone.NotificationPanelView
+<merge
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:systemui="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/notification_panel"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@android:color/transparent">
+    xmlns:systemui="http://schemas.android.com/apk/res-auto">
     <FrameLayout
         android:id="@+id/big_clock_container"
         android:layout_width="match_parent"
@@ -102,4 +97,4 @@
         android:background="@drawable/qs_navbar_scrim" />
 
     <include layout="@layout/status_bar_expanded_plugin_frame"/>
-</com.android.systemui.statusbar.phone.NotificationPanelView>
+</merge>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 9716a00..57834da 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -64,7 +64,7 @@
         sysui:ignoreRightInset="true"
         />
 
-    <include layout="@layout/status_bar_expanded"
+    <ViewStub android:id="@+id/status_bar_expanded"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:visibility="invisible" />
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 52c0a26..4f532b7 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1163,6 +1163,9 @@
     <!-- Section title for notifications that do not vibrate or make noise. [CHAR LIMIT=40] -->
     <string name="notification_section_header_gentle">Silent notifications</string>
 
+    <!-- Section title for conversational notifications. [CHAR LIMIT=40] -->
+    <string name="notification_section_header_conversations">Conversations</string>
+
     <!-- Content description for accessibility: Tapping this button will dismiss all gentle notifications [CHAR LIMIT=NONE] -->
     <string name="accessibility_notification_section_header_gentle_clear_all">Clear all silent notifications</string>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index 2c8f238..ed1cd81 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -168,8 +168,9 @@
      *
      * @param reason a flag indicating which string should be shown, see
      *               {@link KeyguardSecurityView#PROMPT_REASON_NONE},
-     *               {@link KeyguardSecurityView#PROMPT_REASON_RESTART} and
-     *               {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}.
+     *               {@link KeyguardSecurityView#PROMPT_REASON_RESTART},
+     *               {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}, and
+     *               {@link KeyguardSecurityView#PROMPT_REASON_PREPARE_FOR_UPDATE}.
      */
     public void showPromptReason(int reason) {
         mSecurityContainer.showPromptReason(reason);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index f8f3dc8..718bcf1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -137,6 +137,8 @@
                 return R.string.kg_prompt_reason_device_admin;
             case PROMPT_REASON_USER_REQUEST:
                 return R.string.kg_prompt_reason_user_request;
+            case PROMPT_REASON_PREPARE_FOR_UPDATE:
+                return R.string.kg_prompt_reason_prepare_for_update_password;
             case PROMPT_REASON_NONE:
                 return 0;
             default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 9eb168a..48c6bd1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -438,6 +438,10 @@
             case PROMPT_REASON_USER_REQUEST:
                 mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request);
                 break;
+            case PROMPT_REASON_PREPARE_FOR_UPDATE:
+                mSecurityMessageDisplay.setMessage(
+                        R.string.kg_prompt_reason_prepare_for_update_pattern);
+                break;
             case PROMPT_REASON_NONE:
                 break;
             default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index c67decc..6d865ab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -116,6 +116,8 @@
                 return R.string.kg_prompt_reason_device_admin;
             case PROMPT_REASON_USER_REQUEST:
                 return R.string.kg_prompt_reason_user_request;
+            case PROMPT_REASON_PREPARE_FOR_UPDATE:
+                return R.string.kg_prompt_reason_prepare_for_update_pin;
             case PROMPT_REASON_NONE:
                 return 0;
             default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index e108194..09d4d5f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -51,6 +51,11 @@
      */
     int PROMPT_REASON_AFTER_LOCKOUT = 5;
 
+    /***
+     * Strong auth is require to prepare for an unattended update.
+     */
+    int PROMPT_REASON_PREPARE_FOR_UPDATE = 6;
+
     /**
      * Interface back to keyguard to tell it when security
      * @param callback
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 7cd29ea..c7492a2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -344,11 +344,14 @@
         mSavedBubbleKeysPerUser = new SparseSetArray<>();
         mCurrentUserId = mNotifUserManager.getCurrentUserId();
         mNotifUserManager.addUserChangedListener(
-                newUserId -> {
-                    saveBubbles(mCurrentUserId);
-                    mBubbleData.dismissAll(DISMISS_USER_CHANGED);
-                    restoreBubbles(newUserId);
-                    mCurrentUserId = newUserId;
+                new NotificationLockscreenUserManager.UserChangedListener() {
+                    @Override
+                    public void onUserChanged(int newUserId) {
+                        BubbleController.this.saveBubbles(mCurrentUserId);
+                        mBubbleData.dismissAll(DISMISS_USER_CHANGED);
+                        BubbleController.this.restoreBubbles(newUserId);
+                        mCurrentUserId = newUserId;
+                    }
                 });
 
         mUserCreatedBubbles = new HashSet<>();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 8d10552..53a23b8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -20,7 +20,6 @@
 
 import android.app.INotificationManager;
 import android.content.Context;
-import android.content.pm.IPackageManager;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.display.NightDisplayListener;
 import android.os.Handler;
@@ -115,13 +114,6 @@
     /** */
     @Singleton
     @Provides
-    public IPackageManager provideIPackageManager() {
-        return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
-    }
-
-    /** */
-    @Singleton
-    @Provides
     public LayoutInflater providerLayoutInflater(Context context) {
         return LayoutInflater.from(context);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index bb12b53..73b6584 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -27,6 +27,7 @@
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.content.pm.IPackageManager;
 import android.content.res.Resources;
 import android.hardware.SensorPrivacyManager;
 import android.os.Handler;
@@ -45,7 +46,6 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
@@ -73,19 +73,6 @@
 
     @Provides
     @Singleton
-    static ActivityManager provideActivityManager(Context context) {
-        return context.getSystemService(ActivityManager.class);
-    }
-
-
-    @Provides
-    @DisplayId
-    static int provideDisplayId(Context context) {
-        return context.getDisplayId();
-    }
-
-    @Provides
-    @Singleton
     static DevicePolicyManager provideDevicePolicyManager(Context context) {
         return context.getSystemService(DevicePolicyManager.class);
     }
@@ -123,6 +110,13 @@
         return WindowManagerGlobal.getWindowManagerService();
     }
 
+    /** */
+    @Singleton
+    @Provides
+    public IPackageManager provideIPackageManager() {
+        return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+    }
+
     @Singleton
     @Provides
     static KeyguardManager provideKeyguardManager(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 58ddda9..442313d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -35,7 +35,7 @@
 import com.android.systemui.statusbar.notification.people.PeopleHubModule;
 import com.android.systemui.statusbar.phone.KeyguardLiftController;
 import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.phone.StatusBarComponent;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.concurrency.ConcurrencyModule;
 import com.android.systemui.util.sensors.AsyncSensorManager;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c1934c6..8f2c3ed 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -22,6 +22,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
 
 import android.app.ActivityManager;
@@ -86,7 +87,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.StatusBarWindowController;
@@ -670,6 +671,8 @@
                 return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
             } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
+            } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) {
+                return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
             }
             return KeyguardSecurityView.PROMPT_REASON_NONE;
         }
@@ -2102,7 +2105,7 @@
     }
 
     public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
-            ViewGroup container, NotificationPanelViewController panelView,
+            ViewGroup container, NotificationPanelView panelView,
             BiometricUnlockController biometricUnlockController, ViewGroup lockIconContainer,
             View notificationContainer, KeyguardBypassController bypassController) {
         mStatusBarKeyguardViewManagerLazy.get().registerStatusBar(statusBar, container, panelView,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 2005d79..ac05c53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -40,7 +40,7 @@
  * You will probably need to restart systemui for the changes to be picked up:
  *
  * {@code
- *  $ adb shell am crash com.android.systemui
+ *  $ adb shell am restart com.android.systemui
  * }
  */
 @Singleton
@@ -59,6 +59,11 @@
         return getDeviceConfigFlag("notification.newpipeline.enabled", false);
     }
 
+    public boolean isNewNotifPipelineRenderingEnabled() {
+        return isNewNotifPipelineEnabled()
+                && getDeviceConfigFlag("notification.newpipeline.rendering", false);
+    }
+
     private void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
         synchronized (mCachedDeviceConfigFlags) {
             for (String key : properties.getKeyset()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
index 12749fd..525b5b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
@@ -368,14 +368,13 @@
     public static class Builder {
         private final DisplayMetrics mDisplayMetrics;
         float mMaxLengthSeconds;
-        float mSpeedUpFactor;
-        float mX2;
-        float mY2;
+        float mSpeedUpFactor = 0.0f;
+        float mX2 = -1.0f;
+        float mY2 = 1.0f;
 
         @Inject
         public Builder(DisplayMetrics displayMetrics) {
             mDisplayMetrics = displayMetrics;
-            reset();
         }
 
         public Builder setMaxLengthSeconds(float maxLengthSeconds) {
@@ -398,15 +397,6 @@
             return this;
         }
 
-        public Builder reset() {
-            mMaxLengthSeconds = 0;
-            mSpeedUpFactor = 0.0f;
-            mX2 = -1.0f;
-            mY2 = 1.0f;
-
-            return this;
-        }
-
         public FlingAnimationUtils build() {
             return new FlingAnimationUtils(mDisplayMetrics, mMaxLengthSeconds, mSpeedUpFactor,
                     mX2, mY2);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index ff4ce94..ebf7c2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -49,6 +49,12 @@
     /** Adds a listener to be notified when the current user changes. */
     void addUserChangedListener(UserChangedListener listener);
 
+    /**
+     * Removes a listener previously registered with
+     * {@link #addUserChangedListener(UserChangedListener)}
+     */
+    void removeUserChangedListener(UserChangedListener listener);
+
     SparseArray<UserInfo> getCurrentProfiles();
 
     void setLockscreenPublicMode(boolean isProfilePublic, int userId);
@@ -79,6 +85,7 @@
 
     /** Notified when the current user changes. */
     interface UserChangedListener {
-        void onUserChanged(int userId);
+        default void onUserChanged(int userId) {}
+        default void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 2e369b3..976531d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -117,51 +117,63 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
-            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                updateCurrentProfilesCache();
-                Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
+            switch (action) {
+                case Intent.ACTION_USER_SWITCHED:
+                    mCurrentUserId = intent.getIntExtra(
+                            Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL);
+                    updateCurrentProfilesCache();
 
-                updateLockscreenNotificationSetting();
-                updatePublicMode();
-                // The filtering needs to happen before the update call below in order to make sure
-                // the presenter has the updated notifications from the new user
-                getEntryManager().reapplyFilterAndSort("user switched");
-                mPresenter.onUserSwitched(mCurrentUserId);
+                    Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
 
-                for (UserChangedListener listener : mListeners) {
-                    listener.onUserChanged(mCurrentUserId);
-                }
-            } else if (Intent.ACTION_USER_ADDED.equals(action)) {
-                updateCurrentProfilesCache();
-            } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
-                // Start the overview connection to the launcher service
-                Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
-            } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
-                final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
-                final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
-                if (intentSender != null) {
-                    try {
-                        mContext.startIntentSender(intentSender, null, 0, 0, 0);
-                    } catch (IntentSender.SendIntentException e) {
-                        /* ignore */
+                    updateLockscreenNotificationSetting();
+                    updatePublicMode();
+                    // The filtering needs to happen before the update call below in order to
+                    // make sure
+                    // the presenter has the updated notifications from the new user
+                    getEntryManager().reapplyFilterAndSort("user switched");
+                    mPresenter.onUserSwitched(mCurrentUserId);
+
+                    for (UserChangedListener listener : mListeners) {
+                        listener.onUserChanged(mCurrentUserId);
                     }
-                }
-                if (notificationKey != null) {
-                    NotificationEntry entry =
-                            getEntryManager().getActiveNotificationUnfiltered(notificationKey);
-                    final int count = getEntryManager().getActiveNotificationsCount();
-                    final int rank = entry != null ? entry.getRanking().getRank() : 0;
-                    NotificationVisibility.NotificationLocation location =
-                            NotificationLogger.getNotificationLocation(entry);
-                    final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
-                            rank, count, true, location);
-                    try {
-                        mBarService.onNotificationClick(notificationKey, nv);
-                    } catch (RemoteException exception) {
-                        /* ignore */
+                    break;
+                case Intent.ACTION_USER_ADDED:
+                case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
+                case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
+                    updateCurrentProfilesCache();
+                    break;
+                case Intent.ACTION_USER_UNLOCKED:
+                    // Start the overview connection to the launcher service
+                    Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
+                    break;
+                case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
+                    final IntentSender intentSender = intent.getParcelableExtra(
+                            Intent.EXTRA_INTENT);
+                    final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
+                    if (intentSender != null) {
+                        try {
+                            mContext.startIntentSender(intentSender, null, 0, 0, 0);
+                        } catch (IntentSender.SendIntentException e) {
+                            /* ignore */
+                        }
                     }
-                }
+                    if (notificationKey != null) {
+                        NotificationEntry entry =
+                                getEntryManager().getActiveNotificationUnfiltered(notificationKey);
+                        final int count = getEntryManager().getActiveNotificationsCount();
+                        final int rank = entry != null ? entry.getRanking().getRank() : 0;
+                        NotificationVisibility.NotificationLocation location =
+                                NotificationLogger.getNotificationLocation(entry);
+                        final NotificationVisibility nv = NotificationVisibility.obtain(
+                                notificationKey,
+                                rank, count, true, location);
+                        try {
+                            mBarService.onNotificationClick(notificationKey, nv);
+                        } catch (RemoteException exception) {
+                            /* ignore */
+                        }
+                    }
+                    break;
             }
         }
     };
@@ -266,6 +278,8 @@
         filter.addAction(Intent.ACTION_USER_SWITCHED);
         filter.addAction(Intent.ACTION_USER_ADDED);
         filter.addAction(Intent.ACTION_USER_UNLOCKED);
+        filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
+        filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter);
 
         IntentFilter internalFilter = new IntentFilter();
@@ -489,6 +503,11 @@
                 }
             }
         }
+        mMainHandler.post(() -> {
+            for (UserChangedListener listener : mListeners) {
+                listener.onCurrentProfilesChanged(mCurrentProfiles);
+            }
+        });
     }
 
     public boolean isAnyProfilePublicMode() {
@@ -555,6 +574,11 @@
         mListeners.add(listener);
     }
 
+    @Override
+    public void removeUserChangedListener(UserChangedListener listener) {
+        mListeners.remove(listener);
+    }
+
 //    public void updatePublicMode() {
 //        //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns
 //        // false when it should be true. Therefore, if we are not on the SHADE, don't even bother
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 6660569..9b31234 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -37,7 +37,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
 import com.android.systemui.statusbar.phone.StatusBarWindowViewController;
 
 /**
@@ -53,7 +53,7 @@
             CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY
             - 16;
     private static final long LAUNCH_TIMEOUT = 500;
-    private final NotificationPanelViewController mNotificationPanel;
+    private final NotificationPanelView mNotificationPanel;
     private final NotificationListContainer mNotificationContainer;
     private final float mWindowCornerRadius;
     private final StatusBarWindowViewController mStatusBarWindowViewController;
@@ -69,7 +69,7 @@
     public ActivityLaunchAnimator(
             StatusBarWindowViewController statusBarWindowViewController,
             Callback callback,
-            NotificationPanelViewController notificationPanel,
+            NotificationPanelView notificationPanel,
             NotificationListContainer container) {
         mNotificationPanel = notificationPanel;
         mNotificationContainer = container;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 33d97a1..a2578ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -34,6 +34,7 @@
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
@@ -131,6 +132,7 @@
     private final KeyguardEnvironment mKeyguardEnvironment;
     private final NotificationGroupManager mGroupManager;
     private final NotificationRankingManager mRankingManager;
+    private final FeatureFlags mFeatureFlags;
 
     private NotificationPresenter mPresenter;
     private RankingMap mLatestRankingMap;
@@ -170,11 +172,13 @@
             NotifLog notifLog,
             NotificationGroupManager groupManager,
             NotificationRankingManager rankingManager,
-            KeyguardEnvironment keyguardEnvironment) {
+            KeyguardEnvironment keyguardEnvironment,
+            FeatureFlags featureFlags) {
         mNotifLog = notifLog;
         mGroupManager = groupManager;
         mRankingManager = rankingManager;
         mKeyguardEnvironment = keyguardEnvironment;
+        mFeatureFlags = featureFlags;
     }
 
     /** Once called, the NEM will start processing notification events from system server. */
@@ -290,6 +294,10 @@
      * WARNING: this will call back into us.  Don't hold any locks.
      */
     @Override
+    public void handleInflationException(NotificationEntry n, Exception e) {
+        handleInflationException(n.getSbn(), e);
+    }
+
     public void handleInflationException(StatusBarNotification n, Exception e) {
         removeNotificationInternal(
                 n.getKey(), null, null, true /* forceRemove */, false /* removedByUser */,
@@ -529,9 +537,12 @@
         NotificationEntry entry = new NotificationEntry(notification, ranking);
 
         Dependency.get(LeakDetector.class).trackInstance(entry);
+
         // Construct the expanded view.
-        requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
-                REASON_CANCEL));
+        if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
+                    REASON_CANCEL));
+        }
 
         abortExistingInflation(key, "addNotification");
         mPendingNotifications.put(key, entry);
@@ -574,8 +585,11 @@
             listener.onPreEntryUpdated(entry);
         }
 
-        requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
-                REASON_CANCEL));
+        if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
+                    REASON_CANCEL));
+        }
+
         updateNotifications("updateNotificationInternal");
 
         if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java
new file mode 100644
index 0000000..fc04827
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflater.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator;
+
+/**
+ * Used by the {@link PreparationCoordinator}.  When notifications are added or updated, the
+ * NotifInflater is asked to (re)inflated and prepare their views.  This inflation occurs off the
+ * main thread. When the inflation is finished, NotifInflater will trigger its InflationCallback.
+ */
+public interface NotifInflater {
+
+    /**
+     * Callback used when inflation is finished.
+     */
+    void setInflationCallback(InflationCallback callback);
+
+    /**
+     * Called to rebind the entry's views.
+     */
+    void rebindViews(NotificationEntry entry);
+
+    /**
+     * Called to inflate the views of an entry.  Views are not considered inflated until all of its
+     * views are bound. Once all views are inflated, the InflationCallback is triggered.
+     */
+    void inflateViews(NotificationEntry entry);
+
+    /**
+     * Request to stop the inflation of an entry.  For example, called when a notification is
+     * removed and no longer needs to be inflated.
+     */
+    void abortInflation(NotificationEntry entry);
+
+    /**
+     * Callback once all the views are inflated and bound for a given NotificationEntry.
+     */
+    interface InflationCallback {
+        void onInflationFinished(NotificationEntry entry);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
new file mode 100644
index 0000000..0d17557
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+
+import android.os.RemoteException;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Handles notification inflating, rebinding, and inflation aborting.
+ *
+ * Currently a wrapper for NotificationRowBinderImpl.
+ */
+@Singleton
+public class NotifInflaterImpl implements NotifInflater {
+
+    private final IStatusBarService mStatusBarService;
+    private final NotifCollection mNotifCollection;
+
+    private NotificationRowBinderImpl mNotificationRowBinder;
+    private InflationCallback mExternalInflationCallback;
+
+    @Inject
+    public NotifInflaterImpl(
+            IStatusBarService statusBarService,
+            NotifCollection notifCollection) {
+        mStatusBarService = statusBarService;
+        mNotifCollection = notifCollection;
+    }
+
+    /**
+     * Attaches the row binder for inflation.
+     */
+    public void setRowBinder(NotificationRowBinderImpl rowBinder) {
+        mNotificationRowBinder = rowBinder;
+        mNotificationRowBinder.setInflationCallback(mInflationCallback);
+    }
+
+    @Override
+    public void setInflationCallback(InflationCallback callback) {
+        mExternalInflationCallback = callback;
+    }
+
+    @Override
+    public void rebindViews(NotificationEntry entry) {
+        inflateViews(entry);
+    }
+
+    /**
+     * Called to inflate the views of an entry.  Views are not considered inflated until all of its
+     * views are bound.
+     */
+    @Override
+    public void inflateViews(NotificationEntry entry) {
+        try {
+            entry.setHasInflationError(false);
+            requireBinder().inflateViews(entry, getDismissCallback(entry));
+        } catch (InflationException e) {
+            // logged in mInflationCallback.handleInflationException
+        }
+    }
+
+    @Override
+    public void abortInflation(NotificationEntry entry) {
+        entry.abortTask();
+    }
+
+    private Runnable getDismissCallback(NotificationEntry entry) {
+        return new Runnable() {
+            @Override
+            public void run() {
+                int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+                /**
+                 * TODO: determine dismissal surface (ie: shade / headsup / aod)
+                 * see {@link NotificationLogger#logNotificationClear}
+                 */
+                mNotifCollection.dismissNotification(
+                        entry,
+                        0,
+                        new DismissedByUserStats(
+                                dismissalSurface,
+                                DISMISS_SENTIMENT_NEUTRAL,
+                                NotificationVisibility.obtain(entry.getKey(),
+                                        entry.getRanking().getRank(),
+                                        mNotifCollection.getNotifs().size(),
+                                        true,
+                                        NotificationLogger.getNotificationLocation(entry))
+                        ));
+            }
+        };
+    }
+
+    private NotificationRowBinderImpl requireBinder() {
+        if (mNotificationRowBinder == null) {
+            throw new RuntimeException("NotificationRowBinder must be attached before using "
+                    + "NotifInflaterImpl.");
+        }
+        return mNotificationRowBinder;
+    }
+
+    private final NotificationContentInflater.InflationCallback mInflationCallback =
+            new NotificationContentInflater.InflationCallback() {
+                @Override
+                public void handleInflationException(
+                        NotificationEntry entry,
+                        Exception e) {
+                    entry.setHasInflationError(true);
+                    try {
+                        final StatusBarNotification sbn = entry.getSbn();
+                        // report notification inflation errors back up
+                        // to notification delegates
+                        mStatusBarService.onNotificationError(
+                                sbn.getPackageName(),
+                                sbn.getTag(),
+                                sbn.getId(),
+                                sbn.getUid(),
+                                sbn.getInitialPid(),
+                                e.getMessage(),
+                                sbn.getUserId());
+                    } catch (RemoteException ex) {
+                    }
+                }
+
+                @Override
+                public void onAsyncInflationFinished(
+                        NotificationEntry entry,
+                        int inflatedFlags) {
+                    if (mExternalInflationCallback != null) {
+                        mExternalInflationCallback.onInflationFinished(entry);
+                    }
+                }
+            };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 4f4fb24..28e486d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -102,6 +102,9 @@
     /** If this was a group child that was promoted to the top level, then who did the promoting. */
     @Nullable NotifPromoter mNotifPromoter;
 
+    /** If this notification had an issue with inflating. Only used with the NewNotifPipeline **/
+    private boolean mHasInflationError;
+
 
     /*
     * Old members
@@ -576,6 +579,18 @@
         remoteInputTextWhenReset = null;
     }
 
+    void setHasInflationError(boolean hasError) {
+        mHasInflationError = hasError;
+    }
+
+    /**
+     * Whether this notification had an error when attempting to inflate. This is only used in
+     * the NewNotifPipeline
+     */
+    public boolean hasInflationError() {
+        return mHasInflationError;
+    }
+
     public void setHasSentReply() {
         hasSentReply = true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
index 6c93618..6dc647d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
@@ -109,16 +109,18 @@
     public void setUpWithPresenter(NotificationPresenter presenter,
             NotificationListContainer listContainer,
             HeadsUpManager headsUpManager,
-            NotificationContentInflater.InflationCallback inflationCallback,
             BindRowCallback bindRowCallback) {
         mPresenter = presenter;
         mListContainer = listContainer;
         mHeadsUpManager = headsUpManager;
-        mInflationCallback = inflationCallback;
         mBindRowCallback = bindRowCallback;
         mOnAppOpsClickListener = mGutsManager::openGuts;
     }
 
+    public void setInflationCallback(NotificationContentInflater.InflationCallback callback) {
+        mInflationCallback = callback;
+    }
+
     public void setNotificationClicker(NotificationClicker clicker) {
         mNotificationClicker = clicker;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index 13247193..eeb54ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.collection.coordinator;
 
 import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
 import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
@@ -45,14 +46,19 @@
      */
     @Inject
     public NotifCoordinators(
+            FeatureFlags featureFlags,
             KeyguardCoordinator keyguardCoordinator,
             RankingCoordinator rankingCoordinator,
             ForegroundCoordinator foregroundCoordinator,
-            DeviceProvisionedCoordinator deviceProvisionedCoordinator) {
+            DeviceProvisionedCoordinator deviceProvisionedCoordinator,
+            PreparationCoordinator preparationCoordinator) {
         mCoordinators.add(keyguardCoordinator);
         mCoordinators.add(rankingCoordinator);
         mCoordinators.add(foregroundCoordinator);
         mCoordinators.add(deviceProvisionedCoordinator);
+        if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mCoordinators.add(preparationCoordinator);
+        }
         // TODO: add new Coordinators here! (b/145134683, b/112656837)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
new file mode 100644
index 0000000..a14f0e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.NotifInflater;
+import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.logging.NotifEvent;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Kicks off notification inflation and view rebinding when a notification is added or updated.
+ * Aborts inflation when a notification is removed.
+ *
+ * If a notification is not done inflating, this coordinator will filter the notification out
+ * from the NotifListBuilder.
+ */
+@Singleton
+public class PreparationCoordinator implements Coordinator {
+    private static final String TAG = "PreparationCoordinator";
+
+    private final NotifLog mNotifLog;
+    private final NotifInflater mNotifInflater;
+    private final List<NotificationEntry> mPendingNotifications = new ArrayList<>();
+
+    @Inject
+    public PreparationCoordinator(NotifLog notifLog, NotifInflaterImpl notifInflater) {
+        mNotifLog = notifLog;
+        mNotifInflater = notifInflater;
+        mNotifInflater.setInflationCallback(mInflationCallback);
+    }
+
+    @Override
+    public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+        notifCollection.addCollectionListener(mNotifCollectionListener);
+        notifListBuilder.addPreRenderFilter(mNotifInflationErrorFilter);
+        notifListBuilder.addPreRenderFilter(mNotifInflatingFilter);
+    }
+
+    private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() {
+        @Override
+        public void onEntryAdded(NotificationEntry entry) {
+            inflateEntry(entry, "entryAdded");
+        }
+
+        @Override
+        public void onEntryUpdated(NotificationEntry entry) {
+            rebind(entry, "entryUpdated");
+        }
+
+        @Override
+        public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) {
+            abortInflation(entry, "entryRemoved reason=" + reason);
+        }
+    };
+
+    private final NotifFilter mNotifInflationErrorFilter = new NotifFilter(
+            TAG + "InflationError") {
+        /**
+         * Filters out notifications that threw an error when attempting to inflate.
+         */
+        @Override
+        public boolean shouldFilterOut(NotificationEntry entry, long now) {
+            if (entry.hasInflationError()) {
+                mPendingNotifications.remove(entry);
+                return true;
+            }
+            return false;
+        }
+    };
+
+    private final NotifFilter mNotifInflatingFilter = new NotifFilter(TAG + "Inflating") {
+        /**
+         * Filters out notifications that haven't been inflated yet
+         */
+        @Override
+        public boolean shouldFilterOut(NotificationEntry entry, long now) {
+            return mPendingNotifications.contains(entry);
+        }
+    };
+
+    private final NotifInflater.InflationCallback mInflationCallback =
+            new NotifInflater.InflationCallback() {
+        @Override
+        public void onInflationFinished(NotificationEntry entry) {
+            mNotifLog.log(NotifEvent.INFLATED, entry);
+            mPendingNotifications.remove(entry);
+            mNotifInflatingFilter.invalidateList();
+        }
+    };
+
+    private void inflateEntry(NotificationEntry entry, String reason) {
+        abortInflation(entry, reason);
+        mPendingNotifications.add(entry);
+        mNotifInflater.inflateViews(entry);
+    }
+
+    private void rebind(NotificationEntry entry, String reason) {
+        mNotifInflater.rebindViews(entry);
+    }
+
+    private void abortInflation(NotificationEntry entry, String reason) {
+        mNotifLog.log(NotifEvent.INFLATION_ABORTED, reason);
+        entry.abortTask();
+        mPendingNotifications.remove(entry);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
index 5e0bd4f..8d3d0ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
@@ -20,9 +20,12 @@
 
 import com.android.systemui.DumpController;
 import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
 import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
 import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
 import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer;
 
@@ -41,7 +44,9 @@
     private final NotifCollection mNotifCollection;
     private final NotifListBuilderImpl mNotifPipeline;
     private final NotifCoordinators mNotifPluggableCoordinators;
+    private final NotifInflaterImpl mNotifInflater;
     private final DumpController mDumpController;
+    private final FeatureFlags mFeatureFlags;
 
     private final FakePipelineConsumer mFakePipelineConsumer = new FakePipelineConsumer();
 
@@ -51,20 +56,30 @@
             NotifCollection notifCollection,
             NotifListBuilderImpl notifPipeline,
             NotifCoordinators notifCoordinators,
-            DumpController dumpController) {
+            NotifInflaterImpl notifInflater,
+            DumpController dumpController,
+            FeatureFlags featureFlags) {
         mGroupCoalescer = groupCoalescer;
         mNotifCollection = notifCollection;
         mNotifPipeline = notifPipeline;
         mNotifPluggableCoordinators = notifCoordinators;
         mDumpController = dumpController;
+        mNotifInflater = notifInflater;
+        mFeatureFlags = featureFlags;
     }
 
     /** Hooks the new pipeline up to NotificationManager */
     public void initialize(
-            NotificationListener notificationService) {
+            NotificationListener notificationService,
+            NotificationRowBinderImpl rowBinder) {
 
         mDumpController.registerDumpable("NotifPipeline", this);
 
+        // Setup inflation
+        if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mNotifInflater.setRowBinder(rowBinder);
+        }
+
         // Wire up coordinators
         mFakePipelineConsumer.attach(mNotifPipeline);
         mNotifPluggableCoordinators.attach(mNotifCollection, mNotifPipeline);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
index e4a57d7..c6c36ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -112,7 +112,12 @@
             LIFETIME_EXTENDED,
             REMOVE_INTERCEPTED,
             INFLATION_ABORTED,
-            INFLATED
+            INFLATED,
+
+            // GroupCoalescer
+            COALESCED_EVENT,
+            EARLY_BATCH_EMIT,
+            EMIT_EVENT_BATCH
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventType {}
@@ -147,8 +152,10 @@
                     "InflationAborted",
                     "Inflated",
 
+                    // GroupCoalescer labels:
                     "CoalescedEvent",
-                    "EarlyBatchEmit"
+                    "EarlyBatchEmit",
+                    "EmitEventBatch"
             };
 
     private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length;
@@ -174,25 +181,28 @@
     /**
      * Events related to {@link NotificationEntryManager}
      */
-    public static final int NOTIF_ADDED = TOTAL_LIST_BUILDER_EVENT_TYPES;
-    public static final int NOTIF_REMOVED = TOTAL_LIST_BUILDER_EVENT_TYPES + 1;
-    public static final int NOTIF_UPDATED = TOTAL_LIST_BUILDER_EVENT_TYPES + 2;
-    public static final int FILTER = TOTAL_LIST_BUILDER_EVENT_TYPES + 3;
-    public static final int SORT = TOTAL_LIST_BUILDER_EVENT_TYPES + 4;
-    public static final int FILTER_AND_SORT = TOTAL_LIST_BUILDER_EVENT_TYPES + 5;
-    public static final int NOTIF_VISIBILITY_CHANGED = TOTAL_LIST_BUILDER_EVENT_TYPES + 6;
-    public static final int LIFETIME_EXTENDED = TOTAL_LIST_BUILDER_EVENT_TYPES + 7;
+    private static final int NEM_EVENT_START_INDEX = TOTAL_LIST_BUILDER_EVENT_TYPES;
+    public static final int NOTIF_ADDED = NEM_EVENT_START_INDEX;
+    public static final int NOTIF_REMOVED = NEM_EVENT_START_INDEX + 1;
+    public static final int NOTIF_UPDATED = NEM_EVENT_START_INDEX + 2;
+    public static final int FILTER = NEM_EVENT_START_INDEX + 3;
+    public static final int SORT = NEM_EVENT_START_INDEX + 4;
+    public static final int FILTER_AND_SORT = NEM_EVENT_START_INDEX + 5;
+    public static final int NOTIF_VISIBILITY_CHANGED = NEM_EVENT_START_INDEX + 6;
+    public static final int LIFETIME_EXTENDED = NEM_EVENT_START_INDEX + 7;
     // unable to remove notif - removal intercepted by {@link NotificationRemoveInterceptor}
-    public static final int REMOVE_INTERCEPTED = TOTAL_LIST_BUILDER_EVENT_TYPES + 8;
-    public static final int INFLATION_ABORTED = TOTAL_LIST_BUILDER_EVENT_TYPES + 9;
-    public static final int INFLATED = TOTAL_LIST_BUILDER_EVENT_TYPES + 10;
+    public static final int REMOVE_INTERCEPTED = NEM_EVENT_START_INDEX + 8;
+    public static final int INFLATION_ABORTED = NEM_EVENT_START_INDEX + 9;
+    public static final int INFLATED = NEM_EVENT_START_INDEX + 10;
     private static final int TOTAL_NEM_EVENT_TYPES = 11;
 
     /**
      * Events related to {@link GroupCoalescer}
      */
-    public static final int COALESCED_EVENT = TOTAL_NEM_EVENT_TYPES;
-    public static final int EARLY_BATCH_EMIT = TOTAL_NEM_EVENT_TYPES + 1;
-    public static final int EMIT_EVENT_BATCH = TOTAL_NEM_EVENT_TYPES + 2;
-    private static final int TOTAL_COALESCER_EVENT_TYPES = 2;
+    private static final int COALESCER_EVENT_START_INDEX = NEM_EVENT_START_INDEX
+            + TOTAL_NEM_EVENT_TYPES;
+    public static final int COALESCED_EVENT = COALESCER_EVENT_START_INDEX;
+    public static final int EARLY_BATCH_EMIT = COALESCER_EVENT_START_INDEX + 1;
+    public static final int EMIT_EVENT_BATCH = COALESCER_EVENT_START_INDEX + 2;
+    private static final int TOTAL_COALESCER_EVENT_TYPES = 3;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 3e1b5bd..89e5f55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -294,6 +294,9 @@
         }
     }
 
+    /**
+     * Logs Notification inflation error
+     */
     private void logNotificationError(
             StatusBarNotification notification,
             Exception exception) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
index 2c0c942..e81d361 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
@@ -37,7 +37,8 @@
     val key: PersonKey,
     val name: CharSequence,
     val avatar: Drawable,
-    val clickIntent: PendingIntent
+    val clickIntent: PendingIntent,
+    val userId: Int
 )
 
 /** Unique identifier for a Person in PeopleHub. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index 784673e..88b4147 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -17,28 +17,27 @@
 package com.android.systemui.statusbar.notification.people
 
 import android.app.Notification
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.ColorFilter
-import android.graphics.PixelFormat
-import android.graphics.drawable.BitmapDrawable
+import android.content.pm.UserInfo
 import android.graphics.drawable.Drawable
-import android.os.UserHandle
+import android.os.UserManager
 import android.service.notification.StatusBarNotification
-import android.util.TypedValue
+import android.util.SparseArray
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
 import com.android.internal.statusbar.NotificationVisibility
 import com.android.internal.widget.MessagingGroup
-import com.android.launcher3.icons.BaseIconFactory
 import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.NotificationPersonExtractorPlugin
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.notification.NotificationEntryListener
 import com.android.systemui.statusbar.notification.NotificationEntryManager
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.policy.ExtensionController
 import java.util.ArrayDeque
+import java.util.concurrent.Executor
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -52,8 +51,7 @@
 
 @Singleton
 class NotificationPersonExtractorPluginBoundary @Inject constructor(
-    extensionController: ExtensionController,
-    private val context: Context
+    extensionController: ExtensionController
 ) : NotificationPersonExtractor {
 
     private var plugin: NotificationPersonExtractorPlugin? = null
@@ -70,9 +68,8 @@
     }
 
     override fun extractPerson(sbn: StatusBarNotification) =
-            plugin?.extractPerson(sbn)?.let { data ->
-                val badged = addBadgeToDrawable(data.avatar, context, sbn.packageName, sbn.user)
-                PersonModel(data.key, data.name, badged, data.clickIntent)
+            plugin?.extractPerson(sbn)?.run {
+                PersonModel(key, name, avatar, clickIntent, sbn.user.identifier)
             }
 
     override fun extractPersonKey(sbn: StatusBarNotification) = plugin?.extractPersonKey(sbn)
@@ -84,11 +81,16 @@
 @Singleton
 class PeopleHubDataSourceImpl @Inject constructor(
     private val notificationEntryManager: NotificationEntryManager,
-    private val peopleHubManager: PeopleHubManager,
-    private val extractor: NotificationPersonExtractor
+    private val extractor: NotificationPersonExtractor,
+    private val userManager: UserManager,
+    @Background private val bgExecutor: Executor,
+    @Main private val mainExecutor: Executor,
+    private val notifLockscreenUserMgr: NotificationLockscreenUserManager
 ) : DataSource<PeopleHubModel> {
 
+    private var userChangeSubscription: Subscription? = null
     private val dataListeners = mutableListOf<DataListener<PeopleHubModel>>()
+    private val peopleHubManagerForUser = SparseArray<PeopleHubManager>()
 
     private val notificationEntryListener = object : NotificationEntryListener {
         override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) =
@@ -106,31 +108,56 @@
     }
 
     private fun removeVisibleEntry(entry: NotificationEntry) {
-        val key = extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey()
-        if (key?.let(peopleHubManager::removeActivePerson) == true) {
-            updateUi()
+        (extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey())?.let { key ->
+            val userId = entry.sbn.user.identifier
+            bgExecutor.execute {
+                val parentId = userManager.getProfileParent(userId)?.id ?: userId
+                mainExecutor.execute {
+                    if (peopleHubManagerForUser[parentId]?.removeActivePerson(key) == true) {
+                        updateUi()
+                    }
+                }
+            }
         }
     }
 
     private fun addVisibleEntry(entry: NotificationEntry) {
-        val personModel = extractor.extractPerson(entry.sbn) ?: entry.extractPerson()
-        if (personModel?.let(peopleHubManager::addActivePerson) == true) {
-            updateUi()
+        (extractor.extractPerson(entry.sbn) ?: entry.extractPerson())?.let { personModel ->
+            val userId = entry.sbn.user.identifier
+            bgExecutor.execute {
+                val parentId = userManager.getProfileParent(userId)?.id ?: userId
+                mainExecutor.execute {
+                    val manager = peopleHubManagerForUser[parentId]
+                            ?: PeopleHubManager().also { peopleHubManagerForUser.put(parentId, it) }
+                    if (manager.addActivePerson(personModel)) {
+                        updateUi()
+                    }
+                }
+            }
         }
     }
 
     override fun registerListener(listener: DataListener<PeopleHubModel>): Subscription {
-        val registerWithNotificationEntryManager = dataListeners.isEmpty()
+        val register = dataListeners.isEmpty()
         dataListeners.add(listener)
-        if (registerWithNotificationEntryManager) {
+        if (register) {
+            userChangeSubscription = notifLockscreenUserMgr.registerListener(
+                    object : NotificationLockscreenUserManager.UserChangedListener {
+                        override fun onUserChanged(userId: Int) = updateUi()
+                        override fun onCurrentProfilesChanged(
+                            currentProfiles: SparseArray<UserInfo>?
+                        ) = updateUi()
+                    })
             notificationEntryManager.addNotificationEntryListener(notificationEntryListener)
         } else {
-            listener.onDataChanged(peopleHubManager.getPeopleHubModel())
+            getPeopleHubModelForCurrentUser()?.let(listener::onDataChanged)
         }
         return object : Subscription {
             override fun unsubscribe() {
                 dataListeners.remove(listener)
                 if (dataListeners.isEmpty()) {
+                    userChangeSubscription?.unsubscribe()
+                    userChangeSubscription = null
                     notificationEntryManager
                             .removeNotificationEntryListener(notificationEntryListener)
                 }
@@ -138,16 +165,36 @@
         }
     }
 
+    private fun getPeopleHubModelForCurrentUser(): PeopleHubModel? {
+        val currentUserId = notifLockscreenUserMgr.currentUserId
+        val model = peopleHubManagerForUser[currentUserId]?.getPeopleHubModel()
+                ?: return null
+        val currentProfiles = notifLockscreenUserMgr.currentProfiles
+        return model.copy(people = model.people.filter { person ->
+            currentProfiles[person.userId]?.isQuietModeEnabled == false
+        })
+    }
+
     private fun updateUi() {
-        val model = peopleHubManager.getPeopleHubModel()
+        val model = getPeopleHubModelForCurrentUser() ?: return
         for (listener in dataListeners) {
             listener.onDataChanged(model)
         }
     }
 }
 
-@Singleton
-class PeopleHubManager @Inject constructor() {
+private fun NotificationLockscreenUserManager.registerListener(
+    listener: NotificationLockscreenUserManager.UserChangedListener
+): Subscription {
+    addUserChangedListener(listener)
+    return object : Subscription {
+        override fun unsubscribe() {
+            removeUserChangedListener(listener)
+        }
+    }
+}
+
+class PeopleHubManager {
 
     private val activePeople = mutableMapOf<PersonKey, PersonModel>()
     private val inactivePeople = ArrayDeque<PersonModel>(MAX_STORED_INACTIVE_PEOPLE)
@@ -157,7 +204,7 @@
             if (inactivePeople.size >= MAX_STORED_INACTIVE_PEOPLE) {
                 inactivePeople.removeLast()
             }
-            inactivePeople.push(data)
+            inactivePeople.add(data)
             return true
         }
         return false
@@ -190,63 +237,7 @@
             ?: extras.getString(Notification.EXTRA_TITLE)
             ?: return null
     val drawable = extractAvatarFromRow(this) ?: return null
-    val badgedAvatar = addBadgeToDrawable(drawable, row.context, sbn.packageName, sbn.user)
-    return PersonModel(key, name, badgedAvatar, clickIntent)
-}
-
-private fun addBadgeToDrawable(
-    drawable: Drawable,
-    context: Context,
-    packageName: String,
-    user: UserHandle
-): Drawable {
-    val pm = context.packageManager
-    val appInfo = pm.getApplicationInfoAsUser(packageName, 0, user)
-    return object : Drawable() {
-        override fun draw(canvas: Canvas) {
-            val iconBounds = getBounds()
-            val factory = object : BaseIconFactory(
-                    context,
-                    0 /* unused */,
-                    iconBounds.width(),
-                    true) {}
-            val badge = factory.createBadgedIconBitmap(
-                    appInfo.loadIcon(pm),
-                    user,
-                    true,
-                    appInfo.isInstantApp,
-                    null)
-            val badgeDrawable = BitmapDrawable(context.resources, badge.icon)
-                    .apply {
-                        alpha = drawable.alpha
-                        colorFilter = drawable.colorFilter
-                        val badgeWidth = TypedValue.applyDimension(
-                                TypedValue.COMPLEX_UNIT_DIP,
-                                15f,
-                                context.resources.displayMetrics
-                        ).toInt()
-                        setBounds(
-                                iconBounds.left + (iconBounds.width() - badgeWidth),
-                                iconBounds.top + (iconBounds.height() - badgeWidth),
-                                iconBounds.right,
-                                iconBounds.bottom)
-                    }
-            drawable.bounds = iconBounds
-            drawable.draw(canvas)
-            badgeDrawable.draw(canvas)
-        }
-
-        override fun setAlpha(alpha: Int) {
-            drawable.alpha = alpha
-        }
-
-        override fun setColorFilter(colorFilter: ColorFilter?) {
-            drawable.colorFilter = colorFilter
-        }
-
-        @PixelFormat.Opacity
-        override fun getOpacity(): Int = PixelFormat.OPAQUE
-    }
+    return PersonModel(key, name, drawable, clickIntent, sbn.user.identifier)
 }
 
 fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
@@ -272,4 +263,4 @@
         if (isMessagingNotification()) key else null
 
 private fun NotificationEntry.isMessagingNotification() =
-        sbn.notification.notificationStyle == Notification.MessagingStyle::class.java
\ No newline at end of file
+        sbn.notification.notificationStyle == Notification.MessagingStyle::class.java
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 172b72e..30f22ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -397,7 +397,7 @@
                     existingWrapper.onReinflated();
                 }
             } catch (Exception e) {
-                handleInflationError(runningInflations, e, row.getEntry().getSbn(), callback);
+                handleInflationError(runningInflations, e, row.getEntry(), callback);
                 // Add a running inflation to make sure we don't trigger callbacks.
                 // Safe to do because only happens in tests.
                 runningInflations.put(inflationId, new CancellationSignal());
@@ -448,7 +448,7 @@
                     onViewApplied(newView);
                 } catch (Exception anotherException) {
                     runningInflations.remove(inflationId);
-                    handleInflationError(runningInflations, e, row.getEntry().getSbn(),
+                    handleInflationError(runningInflations, e, row.getEntry(),
                             callback);
                 }
             }
@@ -474,7 +474,7 @@
 
     private static void handleInflationError(
             HashMap<Integer, CancellationSignal> runningInflations, Exception e,
-            StatusBarNotification notification, @Nullable InflationCallback callback) {
+            NotificationEntry notification, @Nullable InflationCallback callback) {
         Assert.isMainThread();
         runningInflations.values().forEach(CancellationSignal::cancel);
         if (callback != null) {
@@ -707,7 +707,7 @@
                     + Integer.toHexString(sbn.getId());
             Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
             if (mCallback != null) {
-                mCallback.handleInflationException(sbn,
+                mCallback.handleInflationException(mRow.getEntry(),
                         new InflationException("Couldn't inflate contentViews" + e));
             }
         }
@@ -729,7 +729,7 @@
         }
 
         @Override
-        public void handleInflationException(StatusBarNotification notification, Exception e) {
+        public void handleInflationException(NotificationEntry entry, Exception e) {
             handleError(e);
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index 2fe54c0..9b95bff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.service.notification.StatusBarNotification;
 
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
@@ -138,10 +137,10 @@
         /**
          * Callback for when there is an inflation exception
          *
-         * @param notification notification which failed to inflate content
+         * @param entry notification which failed to inflate content
          * @param e exception
          */
-        void handleInflationException(StatusBarNotification notification, Exception e);
+        void handleInflationException(NotificationEntry entry, Exception e);
 
         /**
          * Callback for after the content views finish inflating.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index 2761689..09c1fad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -187,18 +187,18 @@
     @Override
     public boolean beginsSection(@NonNull View view, @Nullable View previous) {
         boolean begin = false;
-        if (view instanceof ExpandableNotificationRow) {
-            if (previous instanceof ExpandableNotificationRow) {
+        if (view instanceof ActivatableNotificationView) {
+            if (previous instanceof ActivatableNotificationView) {
                 // If we're drawing the first non-person notification, break out a section
-                ExpandableNotificationRow curr = (ExpandableNotificationRow) view;
-                ExpandableNotificationRow prev = (ExpandableNotificationRow) previous;
+                ActivatableNotificationView curr = (ActivatableNotificationView) view;
+                ActivatableNotificationView prev = (ActivatableNotificationView) previous;
 
-                begin =  curr.getEntry().getBucket() != prev.getEntry().getBucket();
+                begin = getBucket(curr) != getBucket(prev);
             }
         }
 
         if (!begin) {
-            begin = view == mGentleHeader || previous == mPeopleHubView;
+            begin = view == mGentleHeader || view == mPeopleHubView;
         }
 
         return begin;
@@ -230,29 +230,42 @@
             return;
         }
 
-        int lastPersonIndex = -1;
-        int firstGentleNotifIndex = -1;
+        boolean peopleNotificationsPresent = false;
+        int firstNonHeadsUpIndex = -1;
+        int firstGentleIndex = -1;
+        int notifCount = 0;
 
         final int n = mParent.getChildCount();
         for (int i = 0; i < n; i++) {
             View child = mParent.getChildAt(i);
-            if (child instanceof ExpandableNotificationRow
-                    && child.getVisibility() != View.GONE) {
+            if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) {
+                notifCount++;
                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+                if (firstNonHeadsUpIndex == -1 && !row.isHeadsUp()) {
+                    firstNonHeadsUpIndex = i;
+                }
                 if (row.getEntry().getBucket() == BUCKET_PEOPLE) {
-                    lastPersonIndex = i;
+                    peopleNotificationsPresent = true;
                 }
                 if (row.getEntry().getBucket() == BUCKET_SILENT) {
-                    firstGentleNotifIndex = i;
+                    firstGentleIndex = i;
                     break;
                 }
             }
         }
 
-        // make room for peopleHub
-        firstGentleNotifIndex += adjustPeopleHubVisibilityAndPosition(lastPersonIndex);
+        if (firstNonHeadsUpIndex == -1) {
+            firstNonHeadsUpIndex = firstGentleIndex != -1 ? firstGentleIndex : notifCount;
+        }
 
-        adjustGentleHeaderVisibilityAndPosition(firstGentleNotifIndex);
+        // make room for peopleHub
+        int offset = adjustPeopleHubVisibilityAndPosition(
+                firstNonHeadsUpIndex, peopleNotificationsPresent);
+        if (firstGentleIndex != -1) {
+            firstGentleIndex += offset;
+        }
+
+        adjustGentleHeaderVisibilityAndPosition(firstGentleIndex);
 
         mGentleHeader.setAreThereDismissableGentleNotifs(
                 mParent.hasActiveClearableNotifications(ROWS_GENTLE));
@@ -294,13 +307,15 @@
         }
     }
 
-    private int adjustPeopleHubVisibilityAndPosition(int lastPersonIndex) {
-        final boolean showPeopleHeader = mPeopleHubVisible
-                && mNumberOfSections > 2
-                && mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
+    private int adjustPeopleHubVisibilityAndPosition(
+            int targetIndex, boolean peopleNotificationsPresent) {
+        final boolean showPeopleHeader = mNumberOfSections > 2
+                && mStatusBarStateController.getState() != StatusBarState.KEYGUARD
+                && (peopleNotificationsPresent || mPeopleHubVisible);
         final int currentHubIndex = mParent.indexOfChild(mPeopleHubView);
         final boolean currentlyVisible = currentHubIndex >= 0;
-        int targetIndex = lastPersonIndex + 1;
+
+        mPeopleHubView.setCanSwipe(showPeopleHeader && !peopleNotificationsPresent);
 
         if (!showPeopleHeader) {
             if (currentlyVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 823dd5a..71342c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -134,7 +134,7 @@
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
 import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -496,7 +496,8 @@
     protected boolean mClearAllEnabled;
 
     private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
-    private NotificationPanelViewController mNotificationPanelController;
+    private NotificationPanelView mNotificationPanel;
+    private final ShadeController mShadeController = Dependency.get(ShadeController.class);
 
     private final NotificationGutsManager mNotificationGutsManager;
     private final NotificationSectionsManager mSectionsManager;
@@ -5518,8 +5519,7 @@
 
         if (viewsToRemove.isEmpty()) {
             if (closeShade) {
-                Dependency.get(ShadeController.class).animateCollapsePanels(
-                        CommandQueue.FLAG_EXCLUDE_NONE);
+                mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
             }
             return;
         }
@@ -5577,12 +5577,11 @@
 
         final Runnable onSlideAwayAnimationComplete = () -> {
             if (closeShade) {
-                Dependency.get(ShadeController.class).addPostCollapseAction(() -> {
+                mShadeController.addPostCollapseAction(() -> {
                     setDismissAllInProgress(false);
                     onAnimationComplete.run();
                 });
-                Dependency.get(ShadeController.class).animateCollapsePanels(
-                        CommandQueue.FLAG_EXCLUDE_NONE);
+                mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
             } else {
                 setDismissAllInProgress(false);
                 onAnimationComplete.run();
@@ -5658,9 +5657,8 @@
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
-    public void setNotificationPanelController(
-            NotificationPanelViewController notificationPanelViewController) {
-        mNotificationPanelController = notificationPanelViewController;
+    public void setNotificationPanel(NotificationPanelView notificationPanelView) {
+        mNotificationPanel = notificationPanelView;
     }
 
     public void updateIconAreaViews() {
@@ -6404,7 +6402,7 @@
 
                 if (!mAmbientState.isDozing() || startingChild != null) {
                     // We have notifications, go to locked shade.
-                    Dependency.get(ShadeController.class).goToLockedShade(startingChild);
+                    mShadeController.goToLockedShade(startingChild);
                     if (startingChild instanceof ExpandableNotificationRow) {
                         ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild;
                         row.onExpandedByGesture(true /* drag down is always an open */);
@@ -6443,7 +6441,7 @@
 
         @Override
         public void setEmptyDragAmount(float amount) {
-            mNotificationPanelController.setEmptyDragAmount(amount);
+            mNotificationPanel.setEmptyDragAmount(amount);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 6d0fcc3..4845ea1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -31,6 +31,7 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 
 class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeActionHelper {
@@ -298,8 +299,8 @@
     @Override
     public Animator getViewTranslationAnimator(View v, float target,
             ValueAnimator.AnimatorUpdateListener listener) {
-        if (v instanceof SwipeableView) {
-            return ((SwipeableView) v).getTranslateViewAnimator(target, listener);
+        if (v instanceof ExpandableNotificationRow) {
+            return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener);
         } else {
             return superGetViewTranslationAnimator(v, target, listener);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
index a079606..e5717ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
@@ -16,38 +16,22 @@
 
 package com.android.systemui.statusbar.notification.stack
 
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.ObjectAnimator
-import android.animation.ValueAnimator
 import android.content.Context
 import android.util.AttributeSet
-import android.util.FloatProperty
 import android.view.View
 import android.view.ViewGroup
 import android.widget.ImageView
-import android.widget.LinearLayout
-import android.widget.TextView
 import com.android.systemui.R
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin
 import com.android.systemui.statusbar.notification.people.DataListener
 import com.android.systemui.statusbar.notification.people.PersonViewModel
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
 
-private val TRANSLATE_CONTENT = object : FloatProperty<PeopleHubView>("translate") {
-    override fun setValue(view: PeopleHubView, value: Float) {
-        view.translation = value
-    }
-
-    override fun get(view: PeopleHubView) = view.translation
-}
-
 class PeopleHubView(context: Context, attrs: AttributeSet) :
         ActivatableNotificationView(context, attrs), SwipeableView {
 
     private lateinit var contents: ViewGroup
     private lateinit var personControllers: List<PersonDataListenerImpl>
-    private var translateAnim: ObjectAnimator? = null
 
     val personViewAdapters: Sequence<DataListener<PersonViewModel?>>
         get() = personControllers.asSequence()
@@ -56,9 +40,10 @@
         super.onFinishInflate()
         contents = requireViewById(R.id.people_list)
         personControllers = (0 until contents.childCount)
+                .reversed()
                 .asSequence()
                 .mapNotNull { idx ->
-                    (contents.getChildAt(idx) as? LinearLayout)?.let(::PersonDataListenerImpl)
+                    (contents.getChildAt(idx) as? ImageView)?.let(::PersonDataListenerImpl)
                 }
                 .toList()
     }
@@ -69,41 +54,32 @@
 
     override fun createMenu(): NotificationMenuRowPlugin? = null
 
-    override fun getTranslateViewAnimator(
-        leftTarget: Float,
-        listener: ValueAnimator.AnimatorUpdateListener?
-    ): Animator =
-            ObjectAnimator
-                    .ofFloat(this, TRANSLATE_CONTENT, leftTarget)
-                    .apply {
-                        listener?.let { addUpdateListener(listener) }
-                        addListener(object : AnimatorListenerAdapter() {
-                            override fun onAnimationEnd(anim: Animator) {
-                                translateAnim = null
-                            }
-                        })
-                    }
-                    .also {
-                        translateAnim?.cancel()
-                        translateAnim = it
-                    }
-
     override fun resetTranslation() {
-        translateAnim?.cancel()
         translationX = 0f
     }
 
-    private inner class PersonDataListenerImpl(val viewGroup: ViewGroup) :
+    override fun setTranslation(translation: Float) {
+        if (canSwipe) {
+            super.setTranslation(translation)
+        }
+    }
+
+    var canSwipe: Boolean = true
+        set(value) {
+            if (field != value) {
+                if (field) {
+                    resetTranslation()
+                }
+                field = value
+            }
+        }
+
+    private inner class PersonDataListenerImpl(val avatarView: ImageView) :
             DataListener<PersonViewModel?> {
 
-        val nameView = viewGroup.requireViewById<TextView>(R.id.person_name)
-        val avatarView = viewGroup.requireViewById<ImageView>(R.id.person_icon)
-
         override fun onDataChanged(data: PersonViewModel?) {
-            viewGroup.visibility = data?.let { View.VISIBLE } ?: View.INVISIBLE
-            nameView.text = data?.name
             avatarView.setImageDrawable(data?.icon)
-            viewGroup.setOnClickListener { data?.onClick?.invoke() }
+            avatarView.setOnClickListener { data?.onClick?.invoke() }
         }
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java
index 6c6ef61..49e59a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
-import android.animation.Animator;
-import android.animation.ValueAnimator;
-
 import androidx.annotation.Nullable;
 
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -32,10 +29,6 @@
     /** Optionally creates a menu for this view. */
     @Nullable NotificationMenuRowPlugin createMenu();
 
-    /** Animator for translating the view, simulating a swipe. */
-    Animator getTranslateViewAnimator(
-            float leftTarget, ValueAnimator.AnimatorUpdateListener listener);
-
     /** Sets the translation amount for an in-progress swipe. */
     void setTranslation(float translate);
 
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 e03db2c..8b31da4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -230,7 +230,7 @@
         // The shelf will be hidden when dozing with a custom clock, we must show notification
         // icons in this occasion.
         if (mStatusBarStateController.isDozing()
-                && mStatusBarComponent.getPanelController().hasCustomClock()) {
+                && mStatusBarComponent.getPanel().hasCustomClock()) {
             state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index accd2a4..ce1123e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -91,7 +91,7 @@
     private final LockscreenLockIconController mLockscreenLockIconController;
     private NotificationIconAreaController mNotificationIconAreaController;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private NotificationPanelViewController mNotificationPanel;
+    private NotificationPanelView mNotificationPanel;
     private View mAmbientIndicationContainer;
     private StatusBar mStatusBar;
 
@@ -141,7 +141,7 @@
             NotificationIconAreaController notificationIconAreaController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             StatusBarWindowViewController statusBarWindowViewController,
-            NotificationPanelViewController notificationPanel, View ambientIndicationContainer) {
+            NotificationPanelView notificationPanel, View ambientIndicationContainer) {
         mStatusBar = statusBar;
         mNotificationIconAreaController = notificationIconAreaController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 7b20a7b..8e5a912 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -58,7 +58,7 @@
     private final View mClockView;
     private final View mOperatorNameView;
     private final DarkIconDispatcher mDarkIconDispatcher;
-    private final NotificationPanelViewController mNotificationPanelViewController;
+    private final NotificationPanelView mPanelView;
     private final Consumer<ExpandableNotificationRow>
             mSetTrackingHeadsUp = this::setTrackingHeadsUp;
     private final Runnable mUpdatePanelTranslation = this::updatePanelTranslation;
@@ -96,14 +96,13 @@
             SysuiStatusBarStateController statusBarStateController,
             KeyguardBypassController keyguardBypassController,
             KeyguardStateController keyguardStateController,
-            NotificationWakeUpCoordinator wakeUpCoordinator, CommandQueue commandQueue,
-            NotificationPanelViewController notificationPanelViewController) {
+            NotificationWakeUpCoordinator wakeUpCoordinator, CommandQueue commandQueue) {
         this(notificationIconAreaController, headsUpManager, statusBarStateController,
                 keyguardBypassController, wakeUpCoordinator, keyguardStateController,
                 commandQueue,
                 statusbarView.findViewById(R.id.heads_up_status_bar_view),
                 statusbarView.findViewById(R.id.notification_stack_scroller),
-                notificationPanelViewController,
+                statusbarView.findViewById(R.id.notification_panel),
                 statusbarView.findViewById(R.id.clock),
                 statusbarView.findViewById(R.id.operator_name_frame),
                 statusbarView.findViewById(R.id.centered_icon_area));
@@ -120,7 +119,7 @@
             CommandQueue commandQueue,
             HeadsUpStatusBarView headsUpStatusBarView,
             NotificationStackScrollLayout stackScroller,
-            NotificationPanelViewController notificationPanelViewController,
+            NotificationPanelView panelView,
             View clockView,
             View operatorNameView,
             View centeredIconView) {
@@ -132,10 +131,10 @@
         headsUpStatusBarView.setOnDrawingRectChangedListener(
                 () -> updateIsolatedIconLocation(true /* requireUpdate */));
         mStackScroller = stackScroller;
-        mNotificationPanelViewController = notificationPanelViewController;
-        notificationPanelViewController.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
-        notificationPanelViewController.addVerticalTranslationListener(mUpdatePanelTranslation);
-        notificationPanelViewController.setHeadsUpAppearanceController(this);
+        mPanelView = panelView;
+        panelView.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
+        panelView.addVerticalTranslationListener(mUpdatePanelTranslation);
+        panelView.setHeadsUpAppearanceController(this);
         mStackScroller.addOnExpandedHeightChangedListener(mSetExpandedHeight);
         mStackScroller.addOnLayoutChangeListener(mStackScrollLayoutChangeListener);
         mStackScroller.setHeadsUpAppearanceController(this);
@@ -170,9 +169,9 @@
         mHeadsUpManager.removeListener(this);
         mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null);
         mWakeUpCoordinator.removeListener(this);
-        mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
-        mNotificationPanelViewController.removeVerticalTranslationListener(mUpdatePanelTranslation);
-        mNotificationPanelViewController.setHeadsUpAppearanceController(null);
+        mPanelView.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
+        mPanelView.removeVerticalTranslationListener(mUpdatePanelTranslation);
+        mPanelView.setHeadsUpAppearanceController(null);
         mStackScroller.removeOnExpandedHeightChangedListener(mSetExpandedHeight);
         mStackScroller.removeOnLayoutChangeListener(mStackScrollLayoutChangeListener);
         mDarkIconDispatcher.removeDarkReceiver(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index c282cb8..ac06d9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -39,12 +39,12 @@
     private boolean mTouchingHeadsUpView;
     private boolean mTrackingHeadsUp;
     private boolean mCollapseSnoozes;
-    private NotificationPanelViewController mPanel;
+    private NotificationPanelView mPanel;
     private ExpandableNotificationRow mPickedChild;
 
     public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager,
             Callback callback,
-            NotificationPanelViewController notificationPanelView) {
+            NotificationPanelView notificationPanelView) {
         mHeadsUpManager = headsUpManager;
         mCallback = callback;
         mPanel = notificationPanelView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index d3e44ea..d95d2b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -155,6 +155,7 @@
     };
 
     private boolean mLeftIsVoiceAssist;
+    private AssistManager mAssistManager;
     private Drawable mLeftAssistIcon;
 
     private IntentButton mRightButton = new DefaultRightButton();
@@ -253,6 +254,7 @@
         mActivityStarter = Dependency.get(ActivityStarter.class);
         mFlashlightController = Dependency.get(FlashlightController.class);
         mAccessibilityController = Dependency.get(AccessibilityController.class);
+        mAssistManager = Dependency.get(AssistManager.class);
         mActivityIntentHelper = new ActivityIntentHelper(getContext());
         updateLeftAffordance();
     }
@@ -549,7 +551,7 @@
         Runnable runnable = new Runnable() {
             @Override
             public void run() {
-                Dependency.get(AssistManager.class).launchVoiceAssistFromKeyguard();
+                mAssistManager.launchVoiceAssistFromKeyguard();
             }
         };
         if (!mKeyguardStateController.canDismissLockScreen()) {
@@ -563,7 +565,7 @@
     }
 
     private boolean canLaunchVoiceAssist() {
-        return Dependency.get(AssistManager.class).canVoiceAssistBeLaunchedFromKeyguard();
+        return mAssistManager.canVoiceAssistBeLaunchedFromKeyguard();
     }
 
     private void launchPhone() {
@@ -645,7 +647,7 @@
         }
         if (mLeftIsVoiceAssist) {
             mLeftPreview = mPreviewInflater.inflatePreviewFromService(
-                    Dependency.get(AssistManager.class).getVoiceInteractorComponentName());
+                    mAssistManager.getVoiceInteractorComponentName());
         } else {
             mLeftPreview = mPreviewInflater.inflatePreview(mLeftButton.getIntent());
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index f7d52b5..ad1aa83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -98,7 +98,12 @@
                 bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
             }
         }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
-        lockscreenUserManager.addUserChangedListener { pendingUnlockType = null }
+        lockscreenUserManager.addUserChangedListener(
+                object : NotificationLockscreenUserManager.UserChangedListener {
+                    override fun onUserChanged(userId: Int) {
+                        pendingUnlockType = null
+                    }
+                })
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index a3f14ba..4e91e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -90,7 +90,7 @@
     private int mContainerTopPadding;
 
     /**
-     * @see NotificationPanelViewController#getExpandedFraction()
+     * @see NotificationPanelView#getExpandedFraction()
      */
     private float mPanelExpansion;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index a3b1b5f..d4cf272 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -350,7 +350,7 @@
             mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;
         }
 
-        mNavigationBarView.setComponents(mStatusBarLazy.get().getPanelController());
+        mNavigationBarView.setComponents(mStatusBarLazy.get().getPanel(), mAssistManager);
         mNavigationBarView.setDisabledFlags(mDisabledFlags1);
         mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
         mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index ba9ba6c..5a1b20d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -67,6 +67,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.assist.AssistHandleViewController;
+import com.android.systemui.assist.AssistManager;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
@@ -147,7 +148,7 @@
 
     private NavigationBarInflaterView mNavigationInflaterView;
     private RecentsOnboarding mRecentsOnboarding;
-    private NotificationPanelViewController mPanelView;
+    private NotificationPanelView mPanelView;
     private FloatingRotationButton mFloatingRotationButton;
     private RotationButtonController mRotationButtonController;
 
@@ -348,7 +349,7 @@
         return mBarTransitions.getLightTransitionsController();
     }
 
-    public void setComponents(NotificationPanelViewController panel) {
+    public void setComponents(NotificationPanelView panel, AssistManager assistManager) {
         mPanelView = panel;
         updatePanelSystemUiStateFlags();
     }
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 9dc8fbf..c74286d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -16,17 +16,118 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
+import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.app.Fragment;
+import android.app.StatusBarManager;
 import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.biometrics.BiometricSourceType;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.FrameLayout;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.keyguard.KeyguardClockSwitch;
+import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.HomeControlsPlugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.qs.QSFragment;
+import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.GestureRecorder;
+import com.android.systemui.statusbar.KeyguardAffordanceView;
+import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.util.InjectionInflationController;
+import com.android.systemui.util.Utils;
 
-public class NotificationPanelView extends PanelView {
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+public class NotificationPanelView extends PanelView implements
+        ExpandableView.OnHeightChangedListener,
+        View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
+        KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener,
+        OnHeadsUpChangedListener, QS.HeightListener, ZenModeController.Callback,
+        ConfigurationController.ConfigurationListener, StateListener,
+        PulseExpansionHandler.ExpansionCallback, DynamicPrivacyController.Listener,
+        NotificationWakeUpCoordinator.WakeUpListener {
 
     private static final boolean DEBUG = false;
 
@@ -35,26 +136,2542 @@
      */
     public static final int FLING_EXPAND = 0;
 
+    /**
+     * Fling collapsing QS, potentially stopping when QS becomes QQS.
+     */
+    public static final int FLING_COLLAPSE = 1;
+
+    /**
+     * Fling until QS is completely hidden.
+     */
+    public static final int FLING_HIDE = 2;
+    private final DozeParameters mDozeParameters;
+
+    private double mQqsSplitFraction;
+
+    // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
+    // changed.
+    private static final int CAP_HEIGHT = 1456;
+    private static final int FONT_HEIGHT = 2163;
+
+    /**
+     * Maximum time before which we will expand the panel even for slow motions when getting a
+     * touch passed over from launcher.
+     */
+    private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300;
+
     static final String COUNTER_PANEL_OPEN = "panel_open";
     static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
+    private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
+
+    private static final Rect mDummyDirtyRect = new Rect(0, 0, 1, 1);
+    private static final Rect mEmptyRect = new Rect();
+
+    private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = new AnimationProperties()
+            .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+    private static final AnimatableProperty KEYGUARD_HEADS_UP_SHOWING_AMOUNT
+            = AnimatableProperty.from("KEYGUARD_HEADS_UP_SHOWING_AMOUNT",
+            NotificationPanelView::setKeyguardHeadsUpShowingAmount,
+            NotificationPanelView::getKeyguardHeadsUpShowingAmount,
+            R.id.keyguard_hun_animator_tag,
+            R.id.keyguard_hun_animator_end_tag,
+            R.id.keyguard_hun_animator_start_tag);
+    private static final AnimationProperties KEYGUARD_HUN_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+    @VisibleForTesting
+    final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
+            new KeyguardUpdateMonitorCallback() {
+
+                @Override
+                public void onBiometricAuthenticated(int userId,
+                        BiometricSourceType biometricSourceType) {
+                    if (mFirstBypassAttempt && mUpdateMonitor.isUnlockingWithBiometricAllowed()) {
+                        mDelayShowingKeyguardStatusBar = true;
+                    }
+                }
+
+                @Override
+                public void onBiometricRunningStateChanged(boolean running,
+                        BiometricSourceType biometricSourceType) {
+                    boolean keyguardOrShadeLocked = mBarState == StatusBarState.KEYGUARD
+                            || mBarState == StatusBarState.SHADE_LOCKED;
+                    if (!running && mFirstBypassAttempt && keyguardOrShadeLocked && !mDozing
+                            && !mDelayShowingKeyguardStatusBar) {
+                        mFirstBypassAttempt = false;
+                        animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+                    }
+                }
+
+                @Override
+                public void onFinishedGoingToSleep(int why) {
+                    mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+                    mDelayShowingKeyguardStatusBar = false;
+                }
+            };
+    private final KeyguardStateController.Callback mKeyguardMonitorCallback =
+            new KeyguardStateController.Callback() {
+                @Override
+                public void onKeyguardFadingAwayChanged() {
+                    if (!mKeyguardStateController.isKeyguardFadingAway()) {
+                        mFirstBypassAttempt = false;
+                        mDelayShowingKeyguardStatusBar = false;
+                    }
+                }
+            };
+
+    private final InjectionInflationController mInjectionInflationController;
+    private final PowerManager mPowerManager;
+    private final AccessibilityManager mAccessibilityManager;
+    private final NotificationWakeUpCoordinator mWakeUpCoordinator;
+    private final PulseExpansionHandler mPulseExpansionHandler;
+    private final KeyguardBypassController mKeyguardBypassController;
+    private final KeyguardUpdateMonitor mUpdateMonitor;
 
     @VisibleForTesting
     protected KeyguardAffordanceHelper mAffordanceHelper;
+    private KeyguardUserSwitcher mKeyguardUserSwitcher;
+    @VisibleForTesting
+    protected KeyguardStatusBarView mKeyguardStatusBar;
+    @VisibleForTesting
+    protected ViewGroup mBigClockContainer;
+    private QS mQs;
+    @VisibleForTesting
+    protected FrameLayout mQsFrame;
+    @VisibleForTesting
+    protected KeyguardStatusView mKeyguardStatusView;
+    private View mQsNavbarScrim;
+    protected NotificationsQuickSettingsContainer mNotificationContainerParent;
+    protected NotificationStackScrollLayout mNotificationStackScroller;
+    protected FrameLayout mHomeControlsLayout;
+    private boolean mAnimateNextPositionUpdate;
 
+    private int mTrackingPointer;
+    private VelocityTracker mQsVelocityTracker;
+    private boolean mQsTracking;
+
+    /**
+     * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
+     * the expansion for quick settings.
+     */
+    private boolean mConflictingQsExpansionGesture;
+
+    /**
+     * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
+     * intercepted yet.
+     */
+    private boolean mIntercepting;
+    private boolean mPanelExpanded;
+    private boolean mQsExpanded;
+    private boolean mQsExpandedWhenExpandingStarted;
+    private boolean mQsFullyExpanded;
+    private boolean mKeyguardShowing;
+    private boolean mDozing;
+    private boolean mDozingOnDown;
+    protected int mBarState;
+    private float mInitialHeightOnTouch;
+    private float mInitialTouchX;
+    private float mInitialTouchY;
+    private float mLastTouchX;
+    private float mLastTouchY;
+    protected float mQsExpansionHeight;
+    protected int mQsMinExpansionHeight;
+    protected int mQsMaxExpansionHeight;
+    private int mQsPeekHeight;
+    private boolean mStackScrollerOverscrolling;
+    private boolean mQsExpansionFromOverscroll;
+    private float mLastOverscroll;
+    protected boolean mQsExpansionEnabled = true;
+    private ValueAnimator mQsExpansionAnimator;
+    private FlingAnimationUtils mFlingAnimationUtils;
+    private int mStatusBarMinHeight;
+    private int mNotificationsHeaderCollideDistance;
+    private int mUnlockMoveDistance;
+    private float mEmptyDragAmount;
+    private float mDownX;
+    private float mDownY;
+
+    private final KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
+            new KeyguardClockPositionAlgorithm();
+    private final KeyguardClockPositionAlgorithm.Result mClockPositionResult =
+            new KeyguardClockPositionAlgorithm.Result();
+    private boolean mIsExpanding;
+
+    private boolean mBlockTouches;
+    // Used for two finger gesture as well as accessibility shortcut to QS.
+    private boolean mQsExpandImmediate;
+    private boolean mTwoFingerQsExpandPossible;
+
+    /**
+     * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
+     * need to take this into account in our panel height calculation.
+     */
+    private boolean mQsAnimatorExpand;
+    private boolean mIsLaunchTransitionFinished;
+    private boolean mIsLaunchTransitionRunning;
+    private Runnable mLaunchAnimationEndRunnable;
+    private boolean mOnlyAffordanceInThisMotion;
+    private boolean mKeyguardStatusViewAnimating;
+    private ValueAnimator mQsSizeChangeAnimator;
+
+    private boolean mShowEmptyShadeView;
+
+    private boolean mQsScrimEnabled = true;
+    private boolean mLastAnnouncementWasQuickSettings;
+    private boolean mQsTouchAboveFalsingThreshold;
+    private int mQsFalsingThreshold;
+
+    private float mKeyguardStatusBarAnimateAlpha = 1f;
     private int mOldLayoutDirection;
+    private HeadsUpTouchHelper mHeadsUpTouchHelper;
+    private boolean mIsExpansionFromHeadsUp;
+    private boolean mListenForHeadsUp;
+    private int mNavigationBarBottomHeight;
+    private boolean mExpandingFromHeadsUp;
+    private boolean mCollapsedOnDown;
+    private int mPositionMinSideMargin;
+    private int mMaxFadeoutHeight;
+    private int mLastOrientation = -1;
+    private boolean mClosingWithAlphaFadeOut;
+    private boolean mHeadsUpAnimatingAway;
+    private boolean mLaunchingAffordance;
+    private boolean mAffordanceHasPreview;
+    private FalsingManager mFalsingManager;
+    private String mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
 
+    private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() {
+        @Override
+        public void run() {
+            setHeadsUpAnimatingAway(false);
+            notifyBarPanelExpansionChanged();
+        }
+    };
+    private NotificationGroupManager mGroupManager;
+    private boolean mShowIconsWhenExpanded;
+    private int mIndicationBottomPadding;
+    private int mAmbientIndicationBottomPadding;
+    private boolean mIsFullWidth;
+    private boolean mBlockingExpansionForCurrentTouch;
+
+    /**
+     * Following variables maintain state of events when input focus transfer may occur.
+     */
+    private boolean mExpectingSynthesizedDown; // expecting to see synthesized DOWN event
+    private boolean mLastEventSynthesizedDown; // last event was synthesized DOWN event
+
+    /**
+     * Current dark amount that follows regular interpolation curve of animation.
+     */
+    private float mInterpolatedDarkAmount;
+
+    /**
+     * Dark amount that animates from 0 to 1 or vice-versa in linear manner, even if the
+     * interpolation curve is different.
+     */
+    private float mLinearDarkAmount;
+
+    private boolean mPulsing;
+    private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+    private boolean mNoVisibleNotifications = true;
+    private boolean mUserSetupComplete;
+    private int mQsNotificationTopPadding;
+    private float mExpandOffset;
+    private boolean mHideIconsDuringNotificationLaunch = true;
+    private int mStackScrollerMeasuringPass;
+    private ArrayList<Consumer<ExpandableNotificationRow>> mTrackingHeadsUpListeners
+            = new ArrayList<>();
+    private ArrayList<Runnable> mVerticalTranslationListener = new ArrayList<>();
+    private HeadsUpAppearanceController mHeadsUpAppearanceController;
+
+    private int mPanelAlpha;
     private int mCurrentPanelAlpha;
     private final Paint mAlphaPaint = new Paint();
-    private boolean mDozing;
+    private Runnable mPanelAlphaEndAction;
+    private float mBottomAreaShadeAlpha;
+    private final ValueAnimator mBottomAreaShadeAlphaAnimator;
+    private AnimatorListenerAdapter mAnimatorListenerAdapter = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (mPanelAlphaEndAction != null) {
+                mPanelAlphaEndAction.run();
+            }
+        }
+    };
+    private final AnimatableProperty PANEL_ALPHA = AnimatableProperty.from(
+            "panelAlpha",
+            NotificationPanelView::setPanelAlphaInternal,
+            NotificationPanelView::getCurrentPanelAlpha,
+            R.id.panel_alpha_animator_tag,
+            R.id.panel_alpha_animator_start_tag,
+            R.id.panel_alpha_animator_end_tag);
+    private final AnimationProperties PANEL_ALPHA_OUT_PROPERTIES = new AnimationProperties()
+            .setDuration(150)
+            .setCustomInterpolator(PANEL_ALPHA.getProperty(), Interpolators.ALPHA_OUT);
+    private final AnimationProperties PANEL_ALPHA_IN_PROPERTIES = new AnimationProperties()
+            .setDuration(200)
+            .setAnimationFinishListener(mAnimatorListenerAdapter)
+            .setCustomInterpolator(PANEL_ALPHA.getProperty(), Interpolators.ALPHA_IN);
+    private final NotificationEntryManager mEntryManager;
 
-    public NotificationPanelView(Context context, AttributeSet attrs) {
-        super(context, attrs);
+    private final CommandQueue mCommandQueue;
+    private final NotificationLockscreenUserManager mLockscreenUserManager;
+    private final ShadeController mShadeController;
+    private int mDisplayId;
+
+    /**
+     * Cache the resource id of the theme to avoid unnecessary work in onThemeChanged.
+     *
+     * onThemeChanged is forced when the theme might not have changed. So, to avoid unncessary
+     * work, check the current id with the cached id.
+     */
+    private int mThemeResId;
+    private KeyguardIndicationController mKeyguardIndicationController;
+    private Consumer<Boolean> mAffordanceLaunchListener;
+    private int mShelfHeight;
+    private Runnable mOnReinflationListener;
+    private int mDarkIconSize;
+    private int mHeadsUpInset;
+    private boolean mHeadsUpPinnedMode;
+    private float mKeyguardHeadsUpShowingAmount = 0.0f;
+    private boolean mShowingKeyguardHeadsUp;
+    private boolean mAllowExpandForSmallExpansion;
+    private Runnable mExpandAfterLayoutRunnable;
+
+    /**
+     * If face auth with bypass is running for the first time after you turn on the screen.
+     * (From aod or screen off)
+     */
+    private boolean mFirstBypassAttempt;
+    /**
+     * If auth happens successfully during {@code mFirstBypassAttempt}, and we should wait until
+     * the keyguard is dismissed to show the status bar.
+     */
+    private boolean mDelayShowingKeyguardStatusBar;
+
+    private PluginManager mPluginManager;
+    private FrameLayout mPluginFrame;
+    private NPVPluginManager mNPVPluginManager;
+
+    @Inject
+    public NotificationPanelView(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
+            InjectionInflationController injectionInflationController,
+            NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler,
+            DynamicPrivacyController dynamicPrivacyController,
+            KeyguardBypassController bypassController, FalsingManager falsingManager,
+            PluginManager pluginManager, ShadeController shadeController,
+            NotificationLockscreenUserManager notificationLockscreenUserManager,
+            NotificationEntryManager notificationEntryManager,
+            KeyguardStateController keyguardStateController,
+            StatusBarStateController statusBarStateController, DozeLog dozeLog,
+            DozeParameters dozeParameters, CommandQueue commandQueue) {
+        super(context, attrs, falsingManager, dozeLog, keyguardStateController,
+                (SysuiStatusBarStateController) statusBarStateController);
         setWillNotDraw(!DEBUG);
+        mInjectionInflationController = injectionInflationController;
+        mFalsingManager = falsingManager;
+        mPowerManager = context.getSystemService(PowerManager.class);
+        mWakeUpCoordinator = coordinator;
+        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+        setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
         mAlphaPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
+        setPanelAlpha(255, false /* animate */);
+        mCommandQueue = commandQueue;
+        mDisplayId = context.getDisplayId();
+        mPulseExpansionHandler = pulseExpansionHandler;
+        mDozeParameters = dozeParameters;
+        pulseExpansionHandler.setPulseExpandAbortListener(() -> {
+            if (mQs != null) {
+                mQs.animateHeaderSlidingOut();
+            }
+        });
+        mThemeResId = context.getThemeResId();
+        mKeyguardBypassController = bypassController;
+        mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+        mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+        mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
+        dynamicPrivacyController.addListener(this);
+
+        mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
+        mBottomAreaShadeAlphaAnimator.addUpdateListener(animation -> {
+            mBottomAreaShadeAlpha = (float) animation.getAnimatedValue();
+            updateKeyguardBottomAreaAlpha();
+        });
+        mBottomAreaShadeAlphaAnimator.setDuration(160);
+        mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT);
+        mPluginManager = pluginManager;
+        mShadeController = shadeController;
+        mLockscreenUserManager = notificationLockscreenUserManager;
+        mEntryManager = notificationEntryManager;
 
         setBackgroundColor(Color.TRANSPARENT);
     }
 
+    /**
+     * Returns if there's a custom clock being presented.
+     */
+    public boolean hasCustomClock() {
+        return mKeyguardStatusView.hasCustomClock();
+    }
+
+    private void setStatusBar(StatusBar bar) {
+        mStatusBar = bar;
+        mKeyguardBottomArea.setStatusBar(mStatusBar);
+    }
+
+    /**
+     * Call after this view has been fully inflated and had its children attached.
+     */
+    public void onChildrenAttached() {
+        loadDimens();
+        mKeyguardStatusBar = findViewById(R.id.keyguard_header);
+        mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
+
+        KeyguardClockSwitch keyguardClockSwitch = findViewById(R.id.keyguard_clock_container);
+        mBigClockContainer = findViewById(R.id.big_clock_container);
+        keyguardClockSwitch.setBigClockContainer(mBigClockContainer);
+
+        mHomeControlsLayout = findViewById(R.id.home_controls_layout);
+        mNotificationContainerParent = findViewById(R.id.notification_container_parent);
+        mNotificationStackScroller = findViewById(R.id.notification_stack_scroller);
+        mNotificationStackScroller.setOnHeightChangedListener(this);
+        mNotificationStackScroller.setOverscrollTopChangedListener(this);
+        mNotificationStackScroller.setOnEmptySpaceClickListener(this);
+        addTrackingHeadsUpListener(mNotificationStackScroller::setTrackingHeadsUp);
+        mKeyguardBottomArea = findViewById(R.id.keyguard_bottom_area);
+        mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
+        mLastOrientation = getResources().getConfiguration().orientation;
+        mPluginFrame = findViewById(R.id.plugin_frame);
+        if (Settings.System.getInt(
+                mContext.getContentResolver(), "npv_plugin_flag", 0) == 1) {
+            mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
+        }
+
+
+        initBottomArea();
+
+        mWakeUpCoordinator.setStackScroller(mNotificationStackScroller);
+        mQsFrame = findViewById(R.id.qs_frame);
+        mPulseExpansionHandler.setUp(mNotificationStackScroller, this, mShadeController);
+        mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() {
+            @Override
+            public void onFullyHiddenChanged(boolean isFullyHidden) {
+                updateKeyguardStatusBarForHeadsUp();
+            }
+
+            @Override
+            public void onPulseExpansionChanged(boolean expandingChanged) {
+                if (mKeyguardBypassController.getBypassEnabled()) {
+                    // Position the notifications while dragging down while pulsing
+                    requestScrollerTopPaddingUpdate(false /* animate */);
+                    updateQSPulseExpansion();
+                }
+            }
+        });
+
+        mPluginManager.addPluginListener(
+                new PluginListener<HomeControlsPlugin>() {
+
+                    @Override
+                    public void onPluginConnected(HomeControlsPlugin plugin,
+                                                  Context pluginContext) {
+                        plugin.sendParentGroup(mHomeControlsLayout);
+                    }
+
+                    @Override
+                    public void onPluginDisconnected(HomeControlsPlugin plugin) {
+
+                    }
+                }, HomeControlsPlugin.class, false);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        FragmentHostManager.get(this).addTagListener(QS.TAG, mFragmentListener);
+        Dependency.get(StatusBarStateController.class).addCallback(this);
+        Dependency.get(ZenModeController.class).addCallback(this);
+        Dependency.get(ConfigurationController.class).addCallback(this);
+        mUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
+        // Theme might have changed between inflating this view and attaching it to the window, so
+        // force a call to onThemeChanged
+        onThemeChanged();
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        FragmentHostManager.get(this).removeTagListener(QS.TAG, mFragmentListener);
+        Dependency.get(StatusBarStateController.class).removeCallback(this);
+        Dependency.get(ZenModeController.class).removeCallback(this);
+        Dependency.get(ConfigurationController.class).removeCallback(this);
+        mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
+    }
+
+    @Override
+    protected void loadDimens() {
+        super.loadDimens();
+        mFlingAnimationUtils = new FlingAnimationUtils(getResources().getDisplayMetrics(), 0.4f);
+        mStatusBarMinHeight = getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height);
+        mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height);
+        mNotificationsHeaderCollideDistance =
+                getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
+        mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
+        mClockPositionAlgorithm.loadDimens(getResources());
+        mQsFalsingThreshold = getResources().getDimensionPixelSize(
+                R.dimen.qs_falsing_threshold);
+        mPositionMinSideMargin = getResources().getDimensionPixelSize(
+                R.dimen.notification_panel_min_side_margin);
+        mMaxFadeoutHeight = getResources().getDimensionPixelSize(
+                R.dimen.max_notification_fadeout_height);
+        mIndicationBottomPadding = getResources().getDimensionPixelSize(
+                R.dimen.keyguard_indication_bottom_padding);
+        mQsNotificationTopPadding = getResources().getDimensionPixelSize(
+                R.dimen.qs_notification_padding);
+        mShelfHeight = getResources().getDimensionPixelSize(R.dimen.notification_shelf_height);
+        mDarkIconSize = getResources().getDimensionPixelSize(
+                R.dimen.status_bar_icon_drawing_size_dark);
+        int statusbarHeight = getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height);
+        mHeadsUpInset = statusbarHeight + getResources().getDimensionPixelSize(
+                R.dimen.heads_up_status_bar_padding);
+        mQqsSplitFraction = ((float) getResources().getInteger(R.integer.qqs_split_fraction)) / (
+                getResources().getInteger(R.integer.qqs_split_fraction)
+                        + getResources().getInteger(R.integer.qs_split_fraction));
+    }
+
+    /**
+     * @see #launchCamera(boolean, int)
+     * @see #setLaunchingAffordance(boolean)
+     */
+    public void setLaunchAffordanceListener(Consumer<Boolean> listener) {
+        mAffordanceLaunchListener = listener;
+    }
+
+    public void updateResources() {
+        Resources res = getResources();
+        int qsWidth = res.getDimensionPixelSize(R.dimen.qs_panel_width);
+        int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
+        FrameLayout.LayoutParams lp =
+                (FrameLayout.LayoutParams) mQsFrame.getLayoutParams();
+        if (lp.width != qsWidth || lp.gravity != panelGravity) {
+            lp.width = qsWidth;
+            lp.gravity = panelGravity;
+            mQsFrame.setLayoutParams(lp);
+        }
+
+        int panelWidth = res.getDimensionPixelSize(R.dimen.notification_panel_width);
+        lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
+        if (lp.width != panelWidth || lp.gravity != panelGravity) {
+            lp.width = panelWidth;
+            lp.gravity = panelGravity;
+            mNotificationStackScroller.setLayoutParams(lp);
+        }
+        int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
+        int topMargin = sideMargin;
+        lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
+        if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
+                || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
+            lp.width = qsWidth;
+            lp.gravity = panelGravity;
+            lp.leftMargin = sideMargin;
+            lp.rightMargin = sideMargin;
+            lp.topMargin = topMargin;
+            mPluginFrame.setLayoutParams(lp);
+        }
+    }
+
+    @Override
+    public void onDensityOrFontScaleChanged() {
+        updateShowEmptyShadeView();
+    }
+
+    @Override
+    public void onThemeChanged() {
+        final int themeResId = getContext().getThemeResId();
+        if (mThemeResId == themeResId) {
+            return;
+        }
+        mThemeResId = themeResId;
+
+        reInflateViews();
+    }
+
+    @Override
+    public void onOverlayChanged() {
+        reInflateViews();
+    }
+
+    private void reInflateViews() {
+        updateShowEmptyShadeView();
+
+        // Re-inflate the status view group.
+        int index = indexOfChild(mKeyguardStatusView);
+        removeView(mKeyguardStatusView);
+        mKeyguardStatusView = (KeyguardStatusView) mInjectionInflationController
+                .injectable(LayoutInflater.from(mContext)).inflate(
+                        R.layout.keyguard_status_view,
+                        this,
+                        false);
+        addView(mKeyguardStatusView, index);
+
+        // Re-associate the clock container with the keyguard clock switch.
+        mBigClockContainer.removeAllViews();
+        KeyguardClockSwitch keyguardClockSwitch = findViewById(R.id.keyguard_clock_container);
+        keyguardClockSwitch.setBigClockContainer(mBigClockContainer);
+
+        // Update keyguard bottom area
+        index = indexOfChild(mKeyguardBottomArea);
+        removeView(mKeyguardBottomArea);
+        KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
+        mKeyguardBottomArea = (KeyguardBottomAreaView) mInjectionInflationController
+                .injectable(LayoutInflater.from(mContext)).inflate(
+                        R.layout.keyguard_bottom_area,
+                        this,
+                        false);
+        mKeyguardBottomArea.initFrom(oldBottomArea);
+        addView(mKeyguardBottomArea, index);
+        initBottomArea();
+        mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
+        onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
+                mStatusBarStateController.getInterpolatedDozeAmount());
+
+        if (mKeyguardStatusBar != null) {
+            mKeyguardStatusBar.onThemeChanged();
+        }
+
+        setKeyguardStatusViewVisibility(mBarState, false, false);
+        setKeyguardBottomAreaVisibility(mBarState, false);
+        if (mOnReinflationListener != null) {
+            mOnReinflationListener.run();
+        }
+        reinflatePluginContainer();
+    }
+
+    @Override
+    public void onUiModeChanged() {
+        reinflatePluginContainer();
+    }
+
+    private void reinflatePluginContainer() {
+        int index = indexOfChild(mPluginFrame);
+        removeView(mPluginFrame);
+        mPluginFrame = (FrameLayout) mInjectionInflationController
+                .injectable(LayoutInflater.from(mContext)).inflate(
+                        R.layout.status_bar_expanded_plugin_frame,
+                        this,
+                        false);
+        addView(mPluginFrame, index);
+
+        Resources res = getResources();
+        int qsWidth = res.getDimensionPixelSize(R.dimen.qs_panel_width);
+        int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
+        FrameLayout.LayoutParams lp;
+        int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
+        int topMargin =
+                res.getDimensionPixelOffset(com.android.internal.R.dimen.quick_qs_total_height);
+        if (Utils.useQsMediaPlayer(mContext)) {
+            topMargin = res.getDimensionPixelOffset(
+                    com.android.internal.R.dimen.quick_qs_total_height_with_media);
+        }
+        lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
+        if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
+                || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
+            lp.width = qsWidth;
+            lp.gravity = panelGravity;
+            lp.leftMargin = sideMargin;
+            lp.rightMargin = sideMargin;
+            lp.topMargin = topMargin;
+            mPluginFrame.setLayoutParams(lp);
+        }
+
+        if (mNPVPluginManager != null) mNPVPluginManager.replaceFrameLayout(mPluginFrame);
+    }
+
+    private void initBottomArea() {
+        mAffordanceHelper = new KeyguardAffordanceHelper(this, getContext(), mFalsingManager);
+        mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
+        mKeyguardBottomArea.setStatusBar(mStatusBar);
+        mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
+    }
+
+    public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
+        mKeyguardIndicationController = indicationController;
+        mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        DejankUtils.startDetectingBlockingIpcs("NVP#onLayout");
+        super.onLayout(changed, left, top, right, bottom);
+        setIsFullWidth(mNotificationStackScroller.getWidth() == getWidth());
+
+        // Update Clock Pivot
+        mKeyguardStatusView.setPivotX(getWidth() / 2);
+        mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f *
+                mKeyguardStatusView.getClockTextSize());
+
+        // Calculate quick setting heights.
+        int oldMaxHeight = mQsMaxExpansionHeight;
+        if (mQs != null) {
+            mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
+            if (mNPVPluginManager != null) {
+                mNPVPluginManager.setYOffset(mQsMinExpansionHeight);
+                mQsMinExpansionHeight += mNPVPluginManager.getHeight();
+            }
+            mQsMaxExpansionHeight = mQs.getDesiredHeight();
+            mNotificationStackScroller.setMaxTopPadding(
+                    mQsMaxExpansionHeight + mQsNotificationTopPadding);
+        }
+        positionClockAndNotifications();
+        if (mQsExpanded && mQsFullyExpanded) {
+            mQsExpansionHeight = mQsMaxExpansionHeight;
+            requestScrollerTopPaddingUpdate(false /* animate */);
+            requestPanelHeightUpdate();
+
+            // Size has changed, start an animation.
+            if (mQsMaxExpansionHeight != oldMaxHeight) {
+                startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
+            }
+        } else if (!mQsExpanded) {
+            setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
+        }
+        updateExpandedHeight(getExpandedHeight());
+        updateHeader();
+
+        // If we are running a size change animation, the animation takes care of the height of
+        // the container. However, if we are not animating, we always need to make the QS container
+        // the desired height so when closing the QS detail, it stays smaller after the size change
+        // animation is finished but the detail view is still being animated away (this animation
+        // takes longer than the size change animation).
+        if (mQsSizeChangeAnimator == null && mQs != null) {
+            mQs.setHeightOverride(mQs.getDesiredHeight());
+        }
+        updateMaxHeadsUpTranslation();
+        updateGestureExclusionRect();
+        if (mExpandAfterLayoutRunnable != null) {
+            mExpandAfterLayoutRunnable.run();
+            mExpandAfterLayoutRunnable = null;
+        }
+        DejankUtils.stopDetectingBlockingIpcs("NVP#onLayout");
+    }
+
+    private void updateGestureExclusionRect() {
+        Rect exclusionRect = calculateGestureExclusionRect();
+        setSystemGestureExclusionRects(exclusionRect.isEmpty()
+                ? Collections.EMPTY_LIST
+                : Collections.singletonList(exclusionRect));
+    }
+
+    private Rect calculateGestureExclusionRect() {
+        Rect exclusionRect = null;
+        Region touchableRegion = mHeadsUpManager.calculateTouchableRegion();
+        if (isFullyCollapsed() && touchableRegion != null) {
+            // Note: The heads up manager also calculates the non-pinned touchable region
+            exclusionRect = touchableRegion.getBounds();
+        }
+        return exclusionRect != null
+                ? exclusionRect
+                : mEmptyRect;
+    }
+
+    private void setIsFullWidth(boolean isFullWidth) {
+        mIsFullWidth = isFullWidth;
+        mNotificationStackScroller.setIsFullWidth(isFullWidth);
+    }
+
+    private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
+        if (mQsSizeChangeAnimator != null) {
+            oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
+            mQsSizeChangeAnimator.cancel();
+        }
+        mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
+        mQsSizeChangeAnimator.setDuration(300);
+        mQsSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                requestScrollerTopPaddingUpdate(false /* animate */);
+                requestPanelHeightUpdate();
+                int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
+                mQs.setHeightOverride(height);
+            }
+        });
+        mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mQsSizeChangeAnimator = null;
+            }
+        });
+        mQsSizeChangeAnimator.start();
+    }
+
+    /**
+     * Positions the clock and notifications dynamically depending on how many notifications are
+     * showing.
+     */
+    private void positionClockAndNotifications() {
+        boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
+        boolean animateClock = animate || mAnimateNextPositionUpdate;
+        int stackScrollerPadding;
+        if (mBarState != StatusBarState.KEYGUARD) {
+            stackScrollerPadding = getUnlockedStackScrollerPadding();
+        } else {
+            int totalHeight = getHeight();
+            int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
+            int clockPreferredY = mKeyguardStatusView.getClockPreferredY(totalHeight);
+            boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
+            final boolean hasVisibleNotifications =
+                    !bypassEnabled && mNotificationStackScroller.getVisibleNotificationCount() != 0;
+            mKeyguardStatusView.setHasVisibleNotifications(hasVisibleNotifications);
+            mClockPositionAlgorithm.setup(
+                    mStatusBarMinHeight,
+                    totalHeight - bottomPadding,
+                    mNotificationStackScroller.getIntrinsicContentHeight(),
+                    getExpandedFraction(),
+                    totalHeight,
+                    (int) (mKeyguardStatusView.getHeight()
+                            - mShelfHeight / 2.0f - mDarkIconSize / 2.0f),
+                    clockPreferredY,
+                    hasCustomClock(),
+                    hasVisibleNotifications,
+                    mInterpolatedDarkAmount,
+                    mEmptyDragAmount,
+                    bypassEnabled,
+                    getUnlockedStackScrollerPadding());
+            mClockPositionAlgorithm.run(mClockPositionResult);
+            PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.X,
+                    mClockPositionResult.clockX, CLOCK_ANIMATION_PROPERTIES, animateClock);
+            PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.Y,
+                    mClockPositionResult.clockY, CLOCK_ANIMATION_PROPERTIES, animateClock);
+            updateNotificationTranslucency();
+            updateClock();
+            stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
+        }
+        mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
+        mKeyguardBottomArea.setAntiBurnInOffsetX(mClockPositionResult.clockX);
+
+        mStackScrollerMeasuringPass++;
+        requestScrollerTopPaddingUpdate(animate);
+        mStackScrollerMeasuringPass = 0;
+        mAnimateNextPositionUpdate = false;
+    }
+
+    /**
+     * @return the padding of the stackscroller when unlocked
+     */
+    private int getUnlockedStackScrollerPadding() {
+        return (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight
+                + mQsNotificationTopPadding;
+    }
+
+    /**
+     * @param maximum the maximum to return at most
+     * @return the maximum keyguard notifications that can fit on the screen
+     */
+    public int computeMaxKeyguardNotifications(int maximum) {
+        float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding();
+        int notificationPadding = Math.max(1, getResources().getDimensionPixelSize(
+                R.dimen.notification_divider_height));
+        NotificationShelf shelf = mNotificationStackScroller.getNotificationShelf();
+        float shelfSize = shelf.getVisibility() == GONE ? 0
+                : shelf.getIntrinsicHeight() + notificationPadding;
+        float availableSpace = mNotificationStackScroller.getHeight() - minPadding - shelfSize
+                - Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding)
+                - mKeyguardStatusView.getLogoutButtonHeight();
+        int count = 0;
+        for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
+            ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
+            if (!(child instanceof ExpandableNotificationRow)) {
+                continue;
+            }
+            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            boolean suppressedSummary = mGroupManager != null
+                    && mGroupManager.isSummaryOfSuppressedGroup(row.getEntry().getSbn());
+            if (suppressedSummary) {
+                continue;
+            }
+            if (!mLockscreenUserManager.shouldShowOnKeyguard(row.getEntry())) {
+                continue;
+            }
+            if (row.isRemoved()) {
+                continue;
+            }
+            availableSpace -= child.getMinHeight(true /* ignoreTemporaryStates */)
+                    + notificationPadding;
+            if (availableSpace >= 0 && count < maximum) {
+                count++;
+            } else if (availableSpace > -shelfSize) {
+                // if we are exactly the last view, then we can show us still!
+                for (int j = i + 1; j < mNotificationStackScroller.getChildCount(); j++) {
+                    if (mNotificationStackScroller.getChildAt(j)
+                            instanceof ExpandableNotificationRow) {
+                        return count;
+                    }
+                }
+                count++;
+                return count;
+            } else {
+                return count;
+            }
+        }
+        return count;
+    }
+
+    private void updateClock() {
+        if (!mKeyguardStatusViewAnimating) {
+            mKeyguardStatusView.setAlpha(mClockPositionResult.clockAlpha);
+        }
+    }
+
+    public void animateToFullShade(long delay) {
+        mNotificationStackScroller.goToFullShade(delay);
+        requestLayout();
+        mAnimateNextPositionUpdate = true;
+    }
+
+    public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
+        mQsExpansionEnabled = qsExpansionEnabled;
+        if (mQs == null) return;
+        mQs.setHeaderClickable(qsExpansionEnabled);
+    }
+
+    @Override
+    public void resetViews(boolean animate) {
+        mIsLaunchTransitionFinished = false;
+        mBlockTouches = false;
+        if (!mLaunchingAffordance) {
+            mAffordanceHelper.reset(false);
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
+        }
+        mStatusBar.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
+                true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
+        if (animate) {
+            animateCloseQs(true /* animateAway */);
+        } else {
+            closeQs();
+        }
+        mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, animate,
+                !animate /* cancelAnimators */);
+        mNotificationStackScroller.resetScrollPosition();
+    }
+
+    @Override
+    public void collapse(boolean delayed, float speedUpFactor) {
+        if (!canPanelBeCollapsed()) {
+            return;
+        }
+
+        if (mQsExpanded) {
+            mQsExpandImmediate = true;
+            mNotificationStackScroller.setShouldShowShelfOnly(true);
+        }
+        super.collapse(delayed, speedUpFactor);
+    }
+
+    public void closeQs() {
+        cancelQsAnimation();
+        setQsExpansion(mQsMinExpansionHeight);
+    }
+
+    /**
+     * Animate QS closing by flinging it.
+     * If QS is expanded, it will collapse into QQS and stop.
+     *
+     * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
+     */
+    public void animateCloseQs(boolean animateAway) {
+        if (mQsExpansionAnimator != null) {
+            if (!mQsAnimatorExpand) {
+                return;
+            }
+            float height = mQsExpansionHeight;
+            mQsExpansionAnimator.cancel();
+            setQsExpansion(height);
+        }
+        flingSettings(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE);
+    }
+
+    public void expandWithQs() {
+        if (mQsExpansionEnabled) {
+            mQsExpandImmediate = true;
+            mNotificationStackScroller.setShouldShowShelfOnly(true);
+        }
+        if (isFullyCollapsed()) {
+            expand(true /* animate */);
+        } else {
+            flingSettings(0 /* velocity */, FLING_EXPAND);
+        }
+    }
+
+    public void expandWithoutQs() {
+        if (isQsExpanded()) {
+            flingSettings(0 /* velocity */, FLING_COLLAPSE);
+        } else {
+            expand(true /* animate */);
+        }
+    }
+
+    @Override
+    public void fling(float vel, boolean expand) {
+        GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
+        if (gr != null) {
+            gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
+        }
+        super.fling(vel, expand);
+    }
+
+    @Override
+    protected void flingToHeight(float vel, boolean expand, float target,
+            float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
+        mHeadsUpTouchHelper.notifyFling(!expand);
+        setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f);
+        super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) {
+            return false;
+        }
+        initDownStates(event);
+        // Do not let touches go to shade or QS if the bouncer is visible,
+        // but still let user swipe down to expand the panel, dismissing the bouncer.
+        if (mStatusBar.isBouncerShowing()) {
+            return true;
+        }
+        if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+            mIsExpansionFromHeadsUp = true;
+            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
+            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
+            return true;
+        }
+        if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
+                && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
+            return true;
+        }
+
+        if (!isFullyCollapsed() && onQsIntercept(event)) {
+            return true;
+        }
+        return super.onInterceptTouchEvent(event);
+    }
+
+    private boolean onQsIntercept(MotionEvent event) {
+        int pointerIndex = event.findPointerIndex(mTrackingPointer);
+        if (pointerIndex < 0) {
+            pointerIndex = 0;
+            mTrackingPointer = event.getPointerId(pointerIndex);
+        }
+        final float x = event.getX(pointerIndex);
+        final float y = event.getY(pointerIndex);
+
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                mIntercepting = true;
+                mInitialTouchY = y;
+                mInitialTouchX = x;
+                initVelocityTracker();
+                trackMovement(event);
+                if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
+                    getParent().requestDisallowInterceptTouchEvent(true);
+                }
+                if (mQsExpansionAnimator != null) {
+                    onQsExpansionStarted();
+                    mInitialHeightOnTouch = mQsExpansionHeight;
+                    mQsTracking = true;
+                    mIntercepting = false;
+                    mNotificationStackScroller.cancelLongPress();
+                }
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                final int upPointer = event.getPointerId(event.getActionIndex());
+                if (mTrackingPointer == upPointer) {
+                    // gesture is ongoing, find a new pointer to track
+                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+                    mTrackingPointer = event.getPointerId(newIndex);
+                    mInitialTouchX = event.getX(newIndex);
+                    mInitialTouchY = event.getY(newIndex);
+                }
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                final float h = y - mInitialTouchY;
+                trackMovement(event);
+                if (mQsTracking) {
+
+                    // Already tracking because onOverscrolled was called. We need to update here
+                    // so we don't stop for a frame until the next touch event gets handled in
+                    // onTouchEvent.
+                    setQsExpansion(h + mInitialHeightOnTouch);
+                    trackMovement(event);
+                    mIntercepting = false;
+                    return true;
+                }
+                if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
+                        && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
+                    mQsTracking = true;
+                    onQsExpansionStarted();
+                    notifyExpandingFinished();
+                    mInitialHeightOnTouch = mQsExpansionHeight;
+                    mInitialTouchY = y;
+                    mInitialTouchX = x;
+                    mIntercepting = false;
+                    mNotificationStackScroller.cancelLongPress();
+                    return true;
+                }
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                trackMovement(event);
+                if (mQsTracking) {
+                    flingQsWithCurrentVelocity(y,
+                            event.getActionMasked() == MotionEvent.ACTION_CANCEL);
+                    mQsTracking = false;
+                }
+                mIntercepting = false;
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    protected boolean isInContentBounds(float x, float y) {
+        float stackScrollerX = mNotificationStackScroller.getX();
+        return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y)
+                && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth();
+    }
+
+    private void initDownStates(MotionEvent event) {
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            mOnlyAffordanceInThisMotion = false;
+            mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
+            mDozingOnDown = isDozing();
+            mDownX = event.getX();
+            mDownY = event.getY();
+            mCollapsedOnDown = isFullyCollapsed();
+            mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
+            mAllowExpandForSmallExpansion = mExpectingSynthesizedDown;
+            mTouchSlopExceededBeforeDown = mExpectingSynthesizedDown;
+            if (mExpectingSynthesizedDown) {
+                mLastEventSynthesizedDown = true;
+            } else {
+                // down but not synthesized motion event.
+                mLastEventSynthesizedDown = false;
+            }
+        } else {
+            // not down event at all.
+            mLastEventSynthesizedDown = false;
+        }
+    }
+
+    private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
+        float vel = getCurrentQSVelocity();
+        final boolean expandsQs = flingExpandsQs(vel);
+        if (expandsQs) {
+            logQsSwipeDown(y);
+        }
+        flingSettings(vel, expandsQs && !isCancelMotionEvent ? FLING_EXPAND : FLING_COLLAPSE);
+    }
+
+    private void logQsSwipeDown(float y) {
+        float vel = getCurrentQSVelocity();
+        final int gesture = mBarState == StatusBarState.KEYGUARD
+                ? MetricsEvent.ACTION_LS_QS
+                : MetricsEvent.ACTION_SHADE_QS_PULL;
+        mLockscreenGestureLogger.write(gesture,
+                (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()),
+                (int) (vel / mStatusBar.getDisplayDensity()));
+    }
+
+    private boolean flingExpandsQs(float vel) {
+        if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) {
+            return false;
+        }
+        if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+            return getQsExpansionFraction() > 0.5f;
+        } else {
+            return vel > 0;
+        }
+    }
+
+    private boolean isFalseTouch() {
+        if (!needsAntiFalsing()) {
+            return false;
+        }
+        if (mFalsingManager.isClassifierEnabled()) {
+            return mFalsingManager.isFalseTouch();
+        }
+        return !mQsTouchAboveFalsingThreshold;
+    }
+
+    private float getQsExpansionFraction() {
+        return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
+                / (mQsMaxExpansionHeight - mQsMinExpansionHeight));
+    }
+
+    @Override
+    protected boolean shouldExpandWhenNotFlinging() {
+        if (super.shouldExpandWhenNotFlinging()) {
+            return true;
+        }
+        if (mAllowExpandForSmallExpansion) {
+            // When we get a touch that came over from launcher, the velocity isn't always correct
+            // Let's err on expanding if the gesture has been reasonably slow
+            long timeSinceDown = SystemClock.uptimeMillis() - mDownTime;
+            return timeSinceDown <= MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER;
+        }
+        return false;
+    }
+
+    @Override
+    protected float getOpeningHeight() {
+        return mNotificationStackScroller.getOpeningHeight();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mBlockTouches || (mQs != null && mQs.isCustomizing())) {
+            return false;
+        }
+
+        // Do not allow panel expansion if bouncer is scrimmed, otherwise user would be able to
+        // pull down QS or expand the shade.
+        if (mStatusBar.isBouncerShowingScrimmed()) {
+            return false;
+        }
+
+        // Make sure the next touch won't the blocked after the current ends.
+        if (event.getAction() == MotionEvent.ACTION_UP
+                || event.getAction() == MotionEvent.ACTION_CANCEL) {
+            mBlockingExpansionForCurrentTouch = false;
+        }
+        // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately
+        // without any ACTION_MOVE event.
+        // In such case, simply expand the panel instead of being stuck at the bottom bar.
+        if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) {
+            expand(true /* animate */);
+        }
+        initDownStates(event);
+        if (!mIsExpanding && !shouldQuickSettingsIntercept(mDownX, mDownY, 0)
+                && mPulseExpansionHandler.onTouchEvent(event)) {
+            // We're expanding all the other ones shouldn't get this anymore
+            return true;
+        }
+        if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
+                && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+            mIsExpansionFromHeadsUp = true;
+            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
+        }
+        boolean handled = false;
+        if ((!mIsExpanding || mHintAnimationRunning)
+                && !mQsExpanded
+                && mBarState != StatusBarState.SHADE
+                && !mDozing) {
+            handled |= mAffordanceHelper.onTouchEvent(event);
+        }
+        if (mOnlyAffordanceInThisMotion) {
+            return true;
+        }
+        handled |= mHeadsUpTouchHelper.onTouchEvent(event);
+
+        if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
+            return true;
+        }
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
+            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
+            updateVerticalPanelPosition(event.getX());
+            handled = true;
+        }
+        handled |= super.onTouchEvent(event);
+        return !mDozing || mPulsing || handled;
+    }
+
+    private boolean handleQsTouch(MotionEvent event) {
+        final int action = event.getActionMasked();
+        if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
+                && mBarState != StatusBarState.KEYGUARD && !mQsExpanded
+                && mQsExpansionEnabled) {
+
+            // Down in the empty area while fully expanded - go to QS.
+            mQsTracking = true;
+            mConflictingQsExpansionGesture = true;
+            onQsExpansionStarted();
+            mInitialHeightOnTouch = mQsExpansionHeight;
+            mInitialTouchY = event.getX();
+            mInitialTouchX = event.getY();
+        }
+        if (!isFullyCollapsed()) {
+            handleQsDown(event);
+        }
+        if (!mQsExpandImmediate && mQsTracking) {
+            onQsTouch(event);
+            if (!mConflictingQsExpansionGesture) {
+                return true;
+            }
+        }
+        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+            mConflictingQsExpansionGesture = false;
+        }
+        if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed()
+                && mQsExpansionEnabled) {
+            mTwoFingerQsExpandPossible = true;
+        }
+        if (mTwoFingerQsExpandPossible && isOpenQsEvent(event)
+                && event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
+            MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_QS, 1);
+            mQsExpandImmediate = true;
+            mNotificationStackScroller.setShouldShowShelfOnly(true);
+            requestPanelHeightUpdate();
+
+            // Normally, we start listening when the panel is expanded, but here we need to start
+            // earlier so the state is already up to date when dragging down.
+            setListening(true);
+        }
+        if (isQsSplitEnabled() && !mKeyguardShowing) {
+            if (mQsExpandImmediate) {
+                mNotificationStackScroller.setVisibility(View.GONE);
+                mQsFrame.setVisibility(View.VISIBLE);
+                mHomeControlsLayout.setVisibility(View.VISIBLE);
+            } else {
+                mNotificationStackScroller.setVisibility(View.VISIBLE);
+                mQsFrame.setVisibility(View.GONE);
+                mHomeControlsLayout.setVisibility(View.GONE);
+            }
+        }
+        return false;
+    }
+
+    private boolean isInQsArea(float x, float y) {
+        return (x >= mQsFrame.getX()
+                && x <= mQsFrame.getX() + mQsFrame.getWidth())
+                && (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
+                || y <= mQs.getView().getY() + mQs.getView().getHeight());
+    }
+
+    private boolean isOnQsEndArea(float x) {
+        if (!isQsSplitEnabled()) return false;
+        if (getLayoutDirection() == LAYOUT_DIRECTION_LTR) {
+            return x >= mQsFrame.getX() + mQqsSplitFraction * mQsFrame.getWidth()
+                    && x <= mQsFrame.getX() + mQsFrame.getWidth();
+        } else {
+            return x >= mQsFrame.getX()
+                    && x <= mQsFrame.getX() + (1 - mQqsSplitFraction) * mQsFrame.getWidth();
+        }
+    }
+
+    private boolean isOpenQsEvent(MotionEvent event) {
+        final int pointerCount = event.getPointerCount();
+        final int action = event.getActionMasked();
+
+        final boolean twoFingerDrag = action == MotionEvent.ACTION_POINTER_DOWN
+                && pointerCount == 2;
+
+        final boolean stylusButtonClickDrag = action == MotionEvent.ACTION_DOWN
+                && (event.isButtonPressed(MotionEvent.BUTTON_STYLUS_PRIMARY)
+                || event.isButtonPressed(MotionEvent.BUTTON_STYLUS_SECONDARY));
+
+        final boolean mouseButtonClickDrag = action == MotionEvent.ACTION_DOWN
+                && (event.isButtonPressed(MotionEvent.BUTTON_SECONDARY)
+                || event.isButtonPressed(MotionEvent.BUTTON_TERTIARY));
+
+        final boolean onHeaderRight = isOnQsEndArea(event.getX());
+
+        return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag || onHeaderRight;
+    }
+
+    private void handleQsDown(MotionEvent event) {
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN
+                && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
+            mFalsingManager.onQsDown();
+            mQsTracking = true;
+            onQsExpansionStarted();
+            mInitialHeightOnTouch = mQsExpansionHeight;
+            mInitialTouchY = event.getX();
+            mInitialTouchX = event.getY();
+
+            // If we interrupt an expansion gesture here, make sure to update the state correctly.
+            notifyExpandingFinished();
+        }
+    }
+
+    /**
+     * Input focus transfer is about to happen.
+     */
+    public void startWaitingForOpenPanelGesture() {
+        if (!isFullyCollapsed()) {
+            return;
+        }
+        mExpectingSynthesizedDown = true;
+        onTrackingStarted();
+        updatePanelExpanded();
+    }
+
+    /**
+     * Called when this view is no longer waiting for input focus transfer.
+     *
+     * There are two scenarios behind this function call. First, input focus transfer
+     * has successfully happened and this view already received synthetic DOWN event.
+     * (mExpectingSynthesizedDown == false). Do nothing.
+     *
+     * Second, before input focus transfer finished, user may have lifted finger
+     * in previous window and this window never received synthetic DOWN event.
+     * (mExpectingSynthesizedDown == true).
+     * In this case, we use the velocity to trigger fling event.
+     *
+     * @param velocity unit is in px / millis
+     */
+    public void stopWaitingForOpenPanelGesture(final float velocity) {
+        if (mExpectingSynthesizedDown) {
+            mExpectingSynthesizedDown = false;
+            maybeVibrateOnOpening();
+            Runnable runnable = () -> fling(velocity > 1f ? 1000f * velocity : 0,
+                    true /* expand */);
+            if (mStatusBar.getStatusBarWindow().getHeight()
+                    != mStatusBar.getStatusBarHeight()) {
+                // The panel is already expanded to its full size, let's expand directly
+                runnable.run();
+            } else {
+                mExpandAfterLayoutRunnable = runnable;
+            }
+            onTrackingStopped(false);
+        }
+    }
+
+    @Override
+    protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
+        boolean expands = super.flingExpands(vel, vectorVel, x, y);
+
+        // If we are already running a QS expansion, make sure that we keep the panel open.
+        if (mQsExpansionAnimator != null) {
+            expands = true;
+        }
+        return expands;
+    }
+
+    @Override
+    protected boolean shouldGestureWaitForTouchSlop() {
+        if (mExpectingSynthesizedDown) {
+            mExpectingSynthesizedDown = false;
+            return false;
+        }
+        return isFullyCollapsed() || mBarState != StatusBarState.SHADE;
+    }
+
+    @Override
+    protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
+        return !mAffordanceHelper.isOnAffordanceIcon(x, y);
+    }
+
+    private void onQsTouch(MotionEvent event) {
+        int pointerIndex = event.findPointerIndex(mTrackingPointer);
+        if (pointerIndex < 0) {
+            pointerIndex = 0;
+            mTrackingPointer = event.getPointerId(pointerIndex);
+        }
+        final float y = event.getY(pointerIndex);
+        final float x = event.getX(pointerIndex);
+        final float h = y - mInitialTouchY;
+
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                mQsTracking = true;
+                mInitialTouchY = y;
+                mInitialTouchX = x;
+                onQsExpansionStarted();
+                mInitialHeightOnTouch = mQsExpansionHeight;
+                initVelocityTracker();
+                trackMovement(event);
+                break;
+
+            case MotionEvent.ACTION_POINTER_UP:
+                final int upPointer = event.getPointerId(event.getActionIndex());
+                if (mTrackingPointer == upPointer) {
+                    // gesture is ongoing, find a new pointer to track
+                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+                    final float newY = event.getY(newIndex);
+                    final float newX = event.getX(newIndex);
+                    mTrackingPointer = event.getPointerId(newIndex);
+                    mInitialHeightOnTouch = mQsExpansionHeight;
+                    mInitialTouchY = newY;
+                    mInitialTouchX = newX;
+                }
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                setQsExpansion(h + mInitialHeightOnTouch);
+                if (h >= getFalsingThreshold()) {
+                    mQsTouchAboveFalsingThreshold = true;
+                }
+                trackMovement(event);
+                break;
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mQsTracking = false;
+                mTrackingPointer = -1;
+                trackMovement(event);
+                float fraction = getQsExpansionFraction();
+                if (fraction != 0f || y >= mInitialTouchY) {
+                    flingQsWithCurrentVelocity(y,
+                            event.getActionMasked() == MotionEvent.ACTION_CANCEL);
+                }
+                if (mQsVelocityTracker != null) {
+                    mQsVelocityTracker.recycle();
+                    mQsVelocityTracker = null;
+                }
+                break;
+        }
+    }
+
+    private int getFalsingThreshold() {
+        float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+        return (int) (mQsFalsingThreshold * factor);
+    }
+
+    @Override
+    public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
+        cancelQsAnimation();
+        if (!mQsExpansionEnabled) {
+            amount = 0f;
+        }
+        float rounded = amount >= 1f ? amount : 0f;
+        setOverScrolling(rounded != 0f && isRubberbanded);
+        mQsExpansionFromOverscroll = rounded != 0f;
+        mLastOverscroll = rounded;
+        updateQsState();
+        setQsExpansion(mQsMinExpansionHeight + rounded);
+    }
+
+    @Override
+    public void flingTopOverscroll(float velocity, boolean open) {
+        mLastOverscroll = 0f;
+        mQsExpansionFromOverscroll = false;
+        setQsExpansion(mQsExpansionHeight);
+        flingSettings(!mQsExpansionEnabled && open ? 0f : velocity,
+                open && mQsExpansionEnabled ? FLING_EXPAND : FLING_COLLAPSE,
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        mStackScrollerOverscrolling = false;
+                        setOverScrolling(false);
+                        updateQsState();
+                    }
+                }, false /* isClick */);
+    }
+
+    private void setOverScrolling(boolean overscrolling) {
+        mStackScrollerOverscrolling = overscrolling;
+        if (mQs == null) return;
+        mQs.setOverscrolling(overscrolling);
+    }
+
+    private void onQsExpansionStarted() {
+        onQsExpansionStarted(0);
+    }
+
+    protected void onQsExpansionStarted(int overscrollAmount) {
+        cancelQsAnimation();
+        cancelHeightAnimator();
+
+        // Reset scroll position and apply that position to the expanded height.
+        float height = mQsExpansionHeight - overscrollAmount;
+        setQsExpansion(height);
+        requestPanelHeightUpdate();
+        mNotificationStackScroller.checkSnoozeLeavebehind();
+
+        // When expanding QS, let's authenticate the user if possible,
+        // this will speed up notification actions.
+        if (height == 0) {
+            mStatusBar.requestFaceAuth();
+        }
+    }
+
+    private void setQsExpanded(boolean expanded) {
+        boolean changed = mQsExpanded != expanded;
+        if (changed) {
+            mQsExpanded = expanded;
+            updateQsState();
+            requestPanelHeightUpdate();
+            mFalsingManager.setQsExpanded(expanded);
+            mStatusBar.setQsExpanded(expanded);
+            mNotificationContainerParent.setQsExpanded(expanded);
+            mPulseExpansionHandler.setQsExpanded(expanded);
+            mKeyguardBypassController.setQSExpanded(expanded);
+        }
+    }
+
+    @Override
+    public void onStateChanged(int statusBarState) {
+        boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
+        boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
+        int oldState = mBarState;
+        boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD;
+        setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
+        setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
+
+        mBarState = statusBarState;
+        mKeyguardShowing = keyguardShowing;
+        if (mKeyguardShowing && isQsSplitEnabled()) {
+            mNotificationStackScroller.setVisibility(View.VISIBLE);
+            mQsFrame.setVisibility(View.VISIBLE);
+            mHomeControlsLayout.setVisibility(View.GONE);
+        }
+
+        if (oldState == StatusBarState.KEYGUARD
+                && (goingToFullShade || statusBarState == StatusBarState.SHADE_LOCKED)) {
+            animateKeyguardStatusBarOut();
+            long delay = mBarState == StatusBarState.SHADE_LOCKED
+                    ? 0 : mKeyguardStateController.calculateGoingToFullShadeDelay();
+            mQs.animateHeaderSlidingIn(delay);
+        } else if (oldState == StatusBarState.SHADE_LOCKED
+                && statusBarState == StatusBarState.KEYGUARD) {
+            animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+            mNotificationStackScroller.resetScrollPosition();
+            // Only animate header if the header is visible. If not, it will partially animate out
+            // the top of QS
+            if (!mQsExpanded) {
+                mQs.animateHeaderSlidingOut();
+            }
+        } else {
+            mKeyguardStatusBar.setAlpha(1f);
+            mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
+            ((PhoneStatusBarView) mBar).maybeShowDivider(keyguardShowing);
+            if (keyguardShowing && oldState != mBarState) {
+                if (mQs != null) {
+                    mQs.hideImmediately();
+                }
+            }
+        }
+        updateKeyguardStatusBarForHeadsUp();
+        if (keyguardShowing) {
+            updateDozingVisibilities(false /* animate */);
+        }
+        // THe update needs to happen after the headerSlide in above, otherwise the translation
+        // would reset
+        updateQSPulseExpansion();
+        maybeAnimateBottomAreaAlpha();
+        resetHorizontalPanelPosition();
+        updateQsState();
+    }
+
+    private void maybeAnimateBottomAreaAlpha() {
+        mBottomAreaShadeAlphaAnimator.cancel();
+        if (mBarState == StatusBarState.SHADE_LOCKED) {
+            mBottomAreaShadeAlphaAnimator.start();
+        } else {
+            mBottomAreaShadeAlpha = 1f;
+        }
+    }
+
+    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mKeyguardStatusViewAnimating = false;
+            mKeyguardStatusView.setVisibility(View.INVISIBLE);
+        }
+    };
+
+    private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mKeyguardStatusViewAnimating = false;
+            mKeyguardStatusView.setVisibility(View.GONE);
+        }
+    };
+
+    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mKeyguardStatusViewAnimating = false;
+        }
+    };
+
+    private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mKeyguardStatusBar.setVisibility(View.INVISIBLE);
+            mKeyguardStatusBar.setAlpha(1f);
+            mKeyguardStatusBarAnimateAlpha = 1f;
+        }
+    };
+
+    private void animateKeyguardStatusBarOut() {
+        ValueAnimator anim = ValueAnimator.ofFloat(mKeyguardStatusBar.getAlpha(), 0f);
+        anim.addUpdateListener(mStatusBarAnimateAlphaListener);
+        anim.setStartDelay(mKeyguardStateController.isKeyguardFadingAway()
+                ? mKeyguardStateController.getKeyguardFadingAwayDelay()
+                : 0);
+
+        long duration;
+        if (mKeyguardStateController.isKeyguardFadingAway()) {
+            duration = mKeyguardStateController.getShortenedFadingAwayDuration();
+        } else {
+            duration = StackStateAnimator.ANIMATION_DURATION_STANDARD;
+        }
+        anim.setDuration(duration);
+
+        anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mAnimateKeyguardStatusBarInvisibleEndRunnable.run();
+            }
+        });
+        anim.start();
+    }
+
+    private final ValueAnimator.AnimatorUpdateListener mStatusBarAnimateAlphaListener =
+            new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
+                    updateHeaderKeyguardAlpha();
+                }
+            };
+
+    private void animateKeyguardStatusBarIn(long duration) {
+        mKeyguardStatusBar.setVisibility(View.VISIBLE);
+        mKeyguardStatusBar.setAlpha(0f);
+        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+        anim.addUpdateListener(mStatusBarAnimateAlphaListener);
+        anim.setDuration(duration);
+        anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+        anim.start();
+    }
+
+    private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mKeyguardBottomArea.setVisibility(View.GONE);
+        }
+    };
+
+    private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
+        mKeyguardBottomArea.animate().cancel();
+        if (goingToFullShade) {
+            mKeyguardBottomArea.animate()
+                    .alpha(0f)
+                    .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+                    .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
+                    .setInterpolator(Interpolators.ALPHA_OUT)
+                    .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable)
+                    .start();
+        } else if (statusBarState == StatusBarState.KEYGUARD
+                || statusBarState == StatusBarState.SHADE_LOCKED) {
+            mKeyguardBottomArea.setVisibility(View.VISIBLE);
+            mKeyguardBottomArea.setAlpha(1f);
+        } else {
+            mKeyguardBottomArea.setVisibility(View.GONE);
+        }
+    }
+
+    private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway,
+            boolean goingToFullShade) {
+        mKeyguardStatusView.animate().cancel();
+        mKeyguardStatusViewAnimating = false;
+        if ((!keyguardFadingAway && mBarState == StatusBarState.KEYGUARD
+                && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) {
+            mKeyguardStatusViewAnimating = true;
+            mKeyguardStatusView.animate()
+                    .alpha(0f)
+                    .setStartDelay(0)
+                    .setDuration(160)
+                    .setInterpolator(Interpolators.ALPHA_OUT)
+                    .withEndAction(mAnimateKeyguardStatusViewGoneEndRunnable);
+            if (keyguardFadingAway) {
+                mKeyguardStatusView.animate()
+                        .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+                        .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
+                        .start();
+            }
+        } else if (mBarState == StatusBarState.SHADE_LOCKED
+                && statusBarState == StatusBarState.KEYGUARD) {
+            mKeyguardStatusView.setVisibility(View.VISIBLE);
+            mKeyguardStatusViewAnimating = true;
+            mKeyguardStatusView.setAlpha(0f);
+            mKeyguardStatusView.animate()
+                    .alpha(1f)
+                    .setStartDelay(0)
+                    .setDuration(320)
+                    .setInterpolator(Interpolators.ALPHA_IN)
+                    .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
+        } else if (statusBarState == StatusBarState.KEYGUARD) {
+            if (keyguardFadingAway) {
+                mKeyguardStatusViewAnimating = true;
+                mKeyguardStatusView.animate()
+                        .alpha(0)
+                        .translationYBy(-getHeight() * 0.05f)
+                        .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
+                        .setDuration(125)
+                        .setStartDelay(0)
+                        .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable)
+                        .start();
+            } else {
+                mKeyguardStatusView.setVisibility(View.VISIBLE);
+                mKeyguardStatusView.setAlpha(1f);
+            }
+        } else {
+            mKeyguardStatusView.setVisibility(View.GONE);
+            mKeyguardStatusView.setAlpha(1f);
+        }
+    }
+
+    private void updateQsState() {
+        mNotificationStackScroller.setQsExpanded(mQsExpanded);
+        mNotificationStackScroller.setScrollingEnabled(
+                mBarState != StatusBarState.KEYGUARD && (!mQsExpanded
+                        || mQsExpansionFromOverscroll));
+        updateEmptyShadeView();
+        if (mNPVPluginManager != null) {
+            mNPVPluginManager.changeVisibility((mBarState != StatusBarState.KEYGUARD)
+                    ? View.VISIBLE
+                    : View.INVISIBLE);
+        }
+        mQsNavbarScrim.setVisibility(mBarState == StatusBarState.SHADE && mQsExpanded
+                && !mStackScrollerOverscrolling && mQsScrimEnabled
+                ? View.VISIBLE
+                : View.INVISIBLE);
+        if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
+            mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
+        }
+        if (mQs == null) return;
+        mQs.setExpanded(mQsExpanded);
+    }
+
+    private void setQsExpansion(float height) {
+        height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
+        mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
+        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling
+                && !mDozing) {
+            setQsExpanded(true);
+        } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
+            setQsExpanded(false);
+        }
+        mQsExpansionHeight = height;
+        updateQsExpansion();
+        requestScrollerTopPaddingUpdate(false /* animate */);
+        updateHeaderKeyguardAlpha();
+        if (mBarState == StatusBarState.SHADE_LOCKED
+                || mBarState == StatusBarState.KEYGUARD) {
+            updateKeyguardBottomAreaAlpha();
+            updateBigClockAlpha();
+        }
+        if (mBarState == StatusBarState.SHADE && mQsExpanded
+                && !mStackScrollerOverscrolling && mQsScrimEnabled) {
+            mQsNavbarScrim.setAlpha(getQsExpansionFraction());
+        }
+
+        if (mAccessibilityManager.isEnabled()) {
+            setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+        }
+
+        if (!mFalsingManager.isUnlockingDisabled() && mQsFullyExpanded
+                && mFalsingManager.shouldEnforceBouncer()) {
+            mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
+                    false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
+        }
+        for (int i = 0; i < mExpansionListeners.size(); i++) {
+            mExpansionListeners.get(i).onQsExpansionChanged(mQsMaxExpansionHeight != 0
+                    ? mQsExpansionHeight / mQsMaxExpansionHeight : 0);
+        }
+        if (DEBUG) {
+            invalidate();
+        }
+    }
+
+    protected void updateQsExpansion() {
+        if (mQs == null) return;
+        float qsExpansionFraction = getQsExpansionFraction();
+        mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
+        int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight();
+        if (mNPVPluginManager != null) {
+            mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff);
+        }
+        mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
+    }
+
+    private String determineAccessibilityPaneTitle() {
+        if (mQs != null && mQs.isCustomizing()) {
+            return getContext().getString(R.string.accessibility_desc_quick_settings_edit);
+        } else if (mQsExpansionHeight != 0.0f && mQsFullyExpanded) {
+            // Upon initialisation when we are not layouted yet we don't want to announce that we
+            // are fully expanded, hence the != 0.0f check.
+            return getContext().getString(R.string.accessibility_desc_quick_settings);
+        } else if (mBarState == StatusBarState.KEYGUARD) {
+            return getContext().getString(R.string.accessibility_desc_lock_screen);
+        } else {
+            return getContext().getString(R.string.accessibility_desc_notification_shade);
+        }
+    }
+
+    private float calculateQsTopPadding() {
+        if (mKeyguardShowing
+                && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
+
+            // Either QS pushes the notifications down when fully expanded, or QS is fully above the
+            // notifications (mostly on tablets). maxNotificationPadding denotes the normal top
+            // padding on Keyguard, maxQsPadding denotes the top padding from the quick settings
+            // panel. We need to take the maximum and linearly interpolate with the panel expansion
+            // for a nice motion.
+            int maxNotificationPadding = getKeyguardNotificationStaticPadding();
+            int maxQsPadding = mQsMaxExpansionHeight + mQsNotificationTopPadding;
+            int max = mBarState == StatusBarState.KEYGUARD
+                    ? Math.max(maxNotificationPadding, maxQsPadding)
+                    : maxQsPadding;
+            return (int) MathUtils.lerp((float) mQsMinExpansionHeight, (float) max,
+                    getExpandedFraction());
+        } else if (mQsSizeChangeAnimator != null) {
+            return Math.max((int) mQsSizeChangeAnimator.getAnimatedValue(),
+                    getKeyguardNotificationStaticPadding());
+        } else if (mKeyguardShowing) {
+            // We can only do the smoother transition on Keyguard when we also are not collapsing
+            // from a scrolled quick settings.
+            return MathUtils.lerp((float) getKeyguardNotificationStaticPadding(),
+                    (float) (mQsMaxExpansionHeight + mQsNotificationTopPadding),
+                    getQsExpansionFraction());
+        } else {
+            return mQsExpansionHeight + mQsNotificationTopPadding;
+        }
+    }
+
+    /**
+     * @return the topPadding of notifications when on keyguard not respecting quick settings
+     *         expansion
+     */
+    private int getKeyguardNotificationStaticPadding() {
+        if (!mKeyguardShowing) {
+            return 0;
+        }
+        if (!mKeyguardBypassController.getBypassEnabled()) {
+            return mClockPositionResult.stackScrollerPadding;
+        }
+        int collapsedPosition = mHeadsUpInset;
+        if (!mNotificationStackScroller.isPulseExpanding()) {
+            return collapsedPosition;
+        } else {
+            int expandedPosition = mClockPositionResult.stackScrollerPadding;
+            return (int) MathUtils.lerp(collapsedPosition, expandedPosition,
+                    mNotificationStackScroller.calculateAppearFractionBypass());
+        }
+    }
+
+
+    protected void requestScrollerTopPaddingUpdate(boolean animate) {
+        mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), animate);
+        if (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()) {
+            // update the position of the header
+            updateQsExpansion();
+        }
+    }
+
+
+    private void updateQSPulseExpansion() {
+        if (mQs != null) {
+            mQs.setShowCollapsedOnKeyguard(mKeyguardShowing
+                    && mKeyguardBypassController.getBypassEnabled()
+                    && mNotificationStackScroller.isPulseExpanding());
+        }
+    }
+
+    private void trackMovement(MotionEvent event) {
+        if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event);
+        mLastTouchX = event.getX();
+        mLastTouchY = event.getY();
+    }
+
+    private void initVelocityTracker() {
+        if (mQsVelocityTracker != null) {
+            mQsVelocityTracker.recycle();
+        }
+        mQsVelocityTracker = VelocityTracker.obtain();
+    }
+
+    private float getCurrentQSVelocity() {
+        if (mQsVelocityTracker == null) {
+            return 0;
+        }
+        mQsVelocityTracker.computeCurrentVelocity(1000);
+        return mQsVelocityTracker.getYVelocity();
+    }
+
+    private void cancelQsAnimation() {
+        if (mQsExpansionAnimator != null) {
+            mQsExpansionAnimator.cancel();
+        }
+    }
+
+    /**
+     * @see #flingSettings(float, int, Runnable, boolean)
+     */
+    public void flingSettings(float vel, int type) {
+        flingSettings(vel, type, null, false /* isClick */);
+    }
+
+    /**
+     * Animates QS or QQS as if the user had swiped up or down.
+     *
+     * @param vel Finger velocity or 0 when not initiated by touch events.
+     * @param type Either {@link #FLING_EXPAND}, {@link #FLING_COLLAPSE} or {@link #FLING_HIDE}.
+     * @param onFinishRunnable Runnable to be executed at the end of animation.
+     * @param isClick If originated by click (different interpolator and duration.)
+     */
+    protected void flingSettings(float vel, int type, final Runnable onFinishRunnable,
+            boolean isClick) {
+        float target;
+        switch (type) {
+            case FLING_EXPAND:
+                target = mQsMaxExpansionHeight;
+                break;
+            case FLING_COLLAPSE:
+                target = mQsMinExpansionHeight;
+                break;
+            case FLING_HIDE:
+            default:
+                target = 0;
+        }
+        if (target == mQsExpansionHeight) {
+            if (onFinishRunnable != null) {
+                onFinishRunnable.run();
+            }
+            return;
+        }
+
+        // If we move in the opposite direction, reset velocity and use a different duration.
+        boolean oppositeDirection = false;
+        boolean expanding = type == FLING_EXPAND;
+        if (vel > 0 && !expanding || vel < 0 && expanding) {
+            vel = 0;
+            oppositeDirection = true;
+        }
+        ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
+        if (isClick) {
+            animator.setInterpolator(Interpolators.TOUCH_RESPONSE);
+            animator.setDuration(368);
+        } else {
+            mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
+        }
+        if (oppositeDirection) {
+            animator.setDuration(350);
+        }
+        animator.addUpdateListener(animation -> {
+            setQsExpansion((Float) animation.getAnimatedValue());
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mNotificationStackScroller.resetCheckSnoozeLeavebehind();
+                mQsExpansionAnimator = null;
+                if (onFinishRunnable != null) {
+                    onFinishRunnable.run();
+                }
+            }
+        });
+        animator.start();
+        mQsExpansionAnimator = animator;
+        mQsAnimatorExpand = expanding;
+    }
+
+    /**
+     * @return Whether we should intercept a gesture to open Quick Settings.
+     */
+    private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
+        if (!mQsExpansionEnabled || mCollapsedOnDown
+                || (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled())) {
+            return false;
+        }
+        View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
+        final boolean onHeader = x >= mQsFrame.getX()
+                && x <= mQsFrame.getX() + mQsFrame.getWidth()
+                && y >= header.getTop() && y <= header.getBottom();
+        if (mQsExpanded) {
+            return onHeader || (yDiff < 0 && isInQsArea(x, y));
+        } else {
+            return onHeader;
+        }
+    }
+
+    @Override
+    protected boolean isScrolledToBottom() {
+        if (!isInSettings()) {
+            return mBarState == StatusBarState.KEYGUARD
+                    || mNotificationStackScroller.isScrolledToBottom();
+        } else {
+            return true;
+        }
+    }
+
+    @Override
+    protected int getMaxPanelHeight() {
+        if (mKeyguardBypassController.getBypassEnabled() && mBarState == StatusBarState.KEYGUARD) {
+            return getMaxPanelHeightBypass();
+        } else {
+            return getMaxPanelHeightNonBypass();
+        }
+    }
+
+    private int getMaxPanelHeightNonBypass() {
+        int min = mStatusBarMinHeight;
+        if (!(mBarState == StatusBarState.KEYGUARD)
+                && mNotificationStackScroller.getNotGoneChildCount() == 0) {
+            int minHeight = (int) (mQsMinExpansionHeight + getOverExpansionAmount());
+            min = Math.max(min, minHeight);
+        }
+        int maxHeight;
+        if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted
+                || mPulsing) {
+            maxHeight = calculatePanelHeightQsExpanded();
+        } else {
+            maxHeight = calculatePanelHeightShade();
+        }
+        maxHeight = Math.max(maxHeight, min);
+        return maxHeight;
+    }
+
+    private int getMaxPanelHeightBypass() {
+        int position = mClockPositionAlgorithm.getExpandedClockPosition()
+                + mKeyguardStatusView.getHeight();
+        if (mNotificationStackScroller.getVisibleNotificationCount() != 0) {
+            position += mShelfHeight / 2.0f + mDarkIconSize / 2.0f;
+        }
+        return position;
+    }
+
+    public boolean isInSettings() {
+        return mQsExpanded;
+    }
+
+    public boolean isExpanding() {
+        return mIsExpanding;
+    }
+
+    @Override
+    protected void onHeightUpdated(float expandedHeight) {
+        if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
+            // Updating the clock position will set the top padding which might
+            // trigger a new panel height and re-position the clock.
+            // This is a circular dependency and should be avoided, otherwise we'll have
+            // a stack overflow.
+            if (mStackScrollerMeasuringPass > 2) {
+                if (DEBUG) Log.d(TAG, "Unstable notification panel height. Aborting.");
+            } else {
+                positionClockAndNotifications();
+            }
+        }
+        if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
+                && !mQsExpansionFromOverscroll) {
+            float t;
+            if (mKeyguardShowing) {
+
+                // On Keyguard, interpolate the QS expansion linearly to the panel expansion
+                t = expandedHeight / (getMaxPanelHeight());
+            } else {
+                // In Shade, interpolate linearly such that QS is closed whenever panel height is
+                // minimum QS expansion + minStackHeight
+                float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
+                        + mNotificationStackScroller.getLayoutMinHeight();
+                float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
+                t = (expandedHeight - panelHeightQsCollapsed)
+                        / (panelHeightQsExpanded - panelHeightQsCollapsed);
+            }
+            float targetHeight = mQsMinExpansionHeight
+                    + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
+            setQsExpansion(targetHeight);
+            mHomeControlsLayout.setTranslationY(targetHeight);
+        }
+        updateExpandedHeight(expandedHeight);
+        updateHeader();
+        updateNotificationTranslucency();
+        updatePanelExpanded();
+        updateGestureExclusionRect();
+        if (DEBUG) {
+            invalidate();
+        }
+    }
+
+    private void updatePanelExpanded() {
+        boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown;
+        if (mPanelExpanded != isExpanded) {
+            mHeadsUpManager.setIsPanelExpanded(isExpanded);
+            mStatusBar.setPanelExpanded(isExpanded);
+            mPanelExpanded = isExpanded;
+        }
+    }
+
+    private int calculatePanelHeightShade() {
+        int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
+        int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin;
+        maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
+
+        if (mBarState == StatusBarState.KEYGUARD) {
+            int minKeyguardPanelBottom = mClockPositionAlgorithm.getExpandedClockPosition()
+                    + mKeyguardStatusView.getHeight()
+                    + mNotificationStackScroller.getIntrinsicContentHeight();
+            return Math.max(maxHeight, minKeyguardPanelBottom);
+        } else {
+            return maxHeight;
+        }
+    }
+
+    private int calculatePanelHeightQsExpanded() {
+        float notificationHeight = mNotificationStackScroller.getHeight()
+                - mNotificationStackScroller.getEmptyBottomMargin()
+                - mNotificationStackScroller.getTopPadding();
+
+        // When only empty shade view is visible in QS collapsed state, simulate that we would have
+        // it in expanded QS state as well so we don't run into troubles when fading the view in/out
+        // and expanding/collapsing the whole panel from/to quick settings.
+        if (mNotificationStackScroller.getNotGoneChildCount() == 0
+                && mShowEmptyShadeView) {
+            notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight();
+        }
+        int maxQsHeight = mQsMaxExpansionHeight;
+
+        if (mKeyguardShowing) {
+            maxQsHeight += mQsNotificationTopPadding;
+        }
+
+        // If an animation is changing the size of the QS panel, take the animated value.
+        if (mQsSizeChangeAnimator != null) {
+            maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
+        }
+        float totalHeight = Math.max(
+                maxQsHeight, mBarState == StatusBarState.KEYGUARD
+                        ? mClockPositionResult.stackScrollerPadding : 0)
+                + notificationHeight + mNotificationStackScroller.getTopPaddingOverflow();
+        if (totalHeight > mNotificationStackScroller.getHeight()) {
+            float fullyCollapsedHeight = maxQsHeight
+                    + mNotificationStackScroller.getLayoutMinHeight();
+            totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
+        }
+        return (int) totalHeight;
+    }
+
+    private void updateNotificationTranslucency() {
+        float alpha = 1f;
+        if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp &&
+                !mHeadsUpManager.hasPinnedHeadsUp()) {
+            alpha = getFadeoutAlpha();
+        }
+        if (mBarState == StatusBarState.KEYGUARD && !mHintAnimationRunning
+                && !mKeyguardBypassController.getBypassEnabled()) {
+            alpha *= mClockPositionResult.clockAlpha;
+        }
+        mNotificationStackScroller.setAlpha(alpha);
+    }
+
+    private float getFadeoutAlpha() {
+        float alpha;
+        if (mQsMinExpansionHeight == 0) {
+            return 1.0f;
+        }
+        alpha = getExpandedHeight() / mQsMinExpansionHeight;
+        alpha = Math.max(0, Math.min(alpha, 1));
+        alpha = (float) Math.pow(alpha, 0.75);
+        return alpha;
+    }
+
+    @Override
+    protected float getOverExpansionAmount() {
+        return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
+    }
+
+    @Override
+    protected float getOverExpansionPixels() {
+        return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
+    }
+
+    /**
+     * Hides the header when notifications are colliding with it.
+     */
+    private void updateHeader() {
+        if (mBarState == StatusBarState.KEYGUARD) {
+            updateHeaderKeyguardAlpha();
+        }
+        updateQsExpansion();
+    }
+
+    protected float getHeaderTranslation() {
+        if (mBarState == StatusBarState.KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
+            return -mQs.getQsMinExpansionHeight();
+        }
+        float appearAmount = mNotificationStackScroller.calculateAppearFraction(mExpandedHeight);
+        float startHeight = -mQsExpansionHeight;
+        if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()
+                && mNotificationStackScroller.isPulseExpanding()) {
+            if (!mPulseExpansionHandler.isExpanding()
+                    && !mPulseExpansionHandler.getLeavingLockscreen()) {
+                // If we aborted the expansion we need to make sure the header doesn't reappear
+                // again after the header has animated away
+                appearAmount = 0;
+            } else {
+                appearAmount = mNotificationStackScroller.calculateAppearFractionBypass();
+            }
+            startHeight = -mQs.getQsMinExpansionHeight();
+            if (mNPVPluginManager != null) startHeight -= mNPVPluginManager.getHeight();
+        }
+        float translation = MathUtils.lerp(startHeight, 0,
+                Math.min(1.0f, appearAmount))
+                + mExpandOffset;
+        return Math.min(0, translation);
+    }
+
+    /**
+     * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area)
+     *         during swiping up
+     */
+    private float getKeyguardContentsAlpha() {
+        float alpha;
+        if (mBarState == StatusBarState.KEYGUARD) {
+
+            // When on Keyguard, we hide the header as soon as we expanded close enough to the
+            // header
+            alpha = getExpandedHeight()
+                    /
+                    (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance);
+        } else {
+
+            // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
+            // soon as we start translating the stack.
+            alpha = getExpandedHeight() / mKeyguardStatusBar.getHeight();
+        }
+        alpha = MathUtils.saturate(alpha);
+        alpha = (float) Math.pow(alpha, 0.75);
+        return alpha;
+    }
+
+    private void updateHeaderKeyguardAlpha() {
+        if (!mKeyguardShowing) {
+            return;
+        }
+        float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
+        float newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
+                * mKeyguardStatusBarAnimateAlpha;
+        newAlpha *= 1.0f - mKeyguardHeadsUpShowingAmount;
+        mKeyguardStatusBar.setAlpha(newAlpha);
+        boolean hideForBypass = mFirstBypassAttempt && mUpdateMonitor.shouldListenForFace()
+                || mDelayShowingKeyguardStatusBar;
+        mKeyguardStatusBar.setVisibility(newAlpha != 0f && !mDozing && !hideForBypass
+                ? VISIBLE : INVISIBLE);
+    }
+
+    private void updateKeyguardBottomAreaAlpha() {
+        // There are two possible panel expansion behaviors:
+        // • User dragging up to unlock: we want to fade out as quick as possible
+        //   (ALPHA_EXPANSION_THRESHOLD) to avoid seeing the bouncer over the bottom area.
+        // • User tapping on lock screen: bouncer won't be visible but panel expansion will
+        //   change due to "unlock hint animation." In this case, fading out the bottom area
+        //   would also hide the message that says "swipe to unlock," we don't want to do that.
+        float expansionAlpha = MathUtils.map(isUnlockHintRunning()
+                        ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f,
+                0f, 1f, getExpandedFraction());
+        float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction());
+        alpha *= mBottomAreaShadeAlpha;
+        mKeyguardBottomArea.setAffordanceAlpha(alpha);
+        mKeyguardBottomArea.setImportantForAccessibility(alpha == 0f
+                ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+        View ambientIndicationContainer = mStatusBar.getAmbientIndicationContainer();
+        if (ambientIndicationContainer != null) {
+            ambientIndicationContainer.setAlpha(alpha);
+        }
+    }
+
+    /**
+     * Custom clock fades away when user drags up to unlock or pulls down quick settings.
+     *
+     * Updates alpha of custom clock to match the alpha of the KeyguardBottomArea. See
+     * {@link updateKeyguardBottomAreaAlpha}.
+     */
+    private void updateBigClockAlpha() {
+        float expansionAlpha = MathUtils.map(isUnlockHintRunning()
+                ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f, getExpandedFraction());
+        float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction());
+        mBigClockContainer.setAlpha(alpha);
+    }
+
+    @Override
+    protected void onExpandingStarted() {
+        super.onExpandingStarted();
+        mNotificationStackScroller.onExpansionStarted();
+        mIsExpanding = true;
+        mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
+        if (mQsExpanded) {
+            onQsExpansionStarted();
+        }
+        // Since there are QS tiles in the header now, we need to make sure we start listening
+        // immediately so they can be up to date.
+        if (mQs == null) return;
+        mQs.setHeaderListening(true);
+    }
+
+    @Override
+    protected void onExpandingFinished() {
+        super.onExpandingFinished();
+        mNotificationStackScroller.onExpansionStopped();
+        mHeadsUpManager.onExpandingFinished();
+        mIsExpanding = false;
+        if (isFullyCollapsed()) {
+            DejankUtils.postAfterTraversal(new Runnable() {
+                @Override
+                public void run() {
+                    setListening(false);
+                }
+            });
+
+            // Workaround b/22639032: Make sure we invalidate something because else RenderThread
+            // thinks we are actually drawing a frame put in reality we don't, so RT doesn't go
+            // ahead with rendering and we jank.
+            postOnAnimation(new Runnable() {
+                @Override
+                public void run() {
+                    getParent().invalidateChild(NotificationPanelView.this, mDummyDirtyRect);
+                }
+            });
+        } else {
+            setListening(true);
+        }
+        mQsExpandImmediate = false;
+        mNotificationStackScroller.setShouldShowShelfOnly(false);
+        mTwoFingerQsExpandPossible = false;
+        mIsExpansionFromHeadsUp = false;
+        notifyListenersTrackingHeadsUp(null);
+        mExpandingFromHeadsUp = false;
+        setPanelScrimMinFraction(0.0f);
+    }
+
+    private void notifyListenersTrackingHeadsUp(ExpandableNotificationRow pickedChild) {
+        for (int i = 0; i < mTrackingHeadsUpListeners.size(); i++) {
+            Consumer<ExpandableNotificationRow> listener
+                    = mTrackingHeadsUpListeners.get(i);
+            listener.accept(pickedChild);
+        }
+    }
+
+    private void setListening(boolean listening) {
+        mKeyguardStatusBar.setListening(listening);
+        if (mQs == null) return;
+        mQs.setListening(listening);
+        if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening);
+    }
+
+    @Override
+    public void expand(boolean animate) {
+        super.expand(animate);
+        setListening(true);
+    }
+
+    @Override
+    protected void setOverExpansion(float overExpansion, boolean isPixels) {
+        if (mConflictingQsExpansionGesture || mQsExpandImmediate) {
+            return;
+        }
+        if (mBarState != StatusBarState.KEYGUARD) {
+            mNotificationStackScroller.setOnHeightChangedListener(null);
+            if (isPixels) {
+                mNotificationStackScroller.setOverScrolledPixels(
+                        overExpansion, true /* onTop */, false /* animate */);
+            } else {
+                mNotificationStackScroller.setOverScrollAmount(
+                        overExpansion, true /* onTop */, false /* animate */);
+            }
+            mNotificationStackScroller.setOnHeightChangedListener(this);
+        }
+    }
+
+    @Override
+    protected void onTrackingStarted() {
+        mFalsingManager.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
+        super.onTrackingStarted();
+        if (mQsFullyExpanded) {
+            mQsExpandImmediate = true;
+            mNotificationStackScroller.setShouldShowShelfOnly(true);
+        }
+        if (mBarState == StatusBarState.KEYGUARD
+                || mBarState == StatusBarState.SHADE_LOCKED) {
+            mAffordanceHelper.animateHideLeftRightIcon();
+        }
+        mNotificationStackScroller.onPanelTrackingStarted();
+    }
+
+    @Override
+    protected void onTrackingStopped(boolean expand) {
+        mFalsingManager.onTrackingStopped();
+        super.onTrackingStopped(expand);
+        if (expand) {
+            mNotificationStackScroller.setOverScrolledPixels(
+                    0.0f, true /* onTop */, true /* animate */);
+        }
+        mNotificationStackScroller.onPanelTrackingStopped();
+        if (expand && (mBarState == StatusBarState.KEYGUARD
+                || mBarState == StatusBarState.SHADE_LOCKED)) {
+            if (!mHintAnimationRunning) {
+                mAffordanceHelper.reset(true);
+            }
+        }
+    }
+
+    @Override
+    public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
+
+        // Block update if we are in quick settings and just the top padding changed
+        // (i.e. view == null).
+        if (view == null && mQsExpanded) {
+            return;
+        }
+        if (needsAnimation && mInterpolatedDarkAmount == 0) {
+            mAnimateNextPositionUpdate = true;
+        }
+        ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
+        ExpandableNotificationRow firstRow = firstChildNotGone instanceof ExpandableNotificationRow
+                ? (ExpandableNotificationRow) firstChildNotGone
+                : null;
+        if (firstRow != null
+                && (view == firstRow || (firstRow.getNotificationParent() == firstRow))) {
+            requestScrollerTopPaddingUpdate(false /* animate */);
+        }
+        requestPanelHeightUpdate();
+    }
+
+    @Override
+    public void onReset(ExpandableView view) {
+    }
+
+    public void onQsHeightChanged() {
+        mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
+        if (mQsExpanded && mQsFullyExpanded) {
+            mQsExpansionHeight = mQsMaxExpansionHeight;
+            requestScrollerTopPaddingUpdate(false /* animate */);
+            requestPanelHeightUpdate();
+        }
+        if (mAccessibilityManager.isEnabled()) {
+            setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+        }
+        mNotificationStackScroller.setMaxTopPadding(
+                mQsMaxExpansionHeight + mQsNotificationTopPadding);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mAffordanceHelper.onConfigurationChanged();
+        if (newConfig.orientation != mLastOrientation) {
+            resetHorizontalPanelPosition();
+        }
+        mLastOrientation = newConfig.orientation;
+    }
+
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        mNavigationBarBottomHeight = insets.getStableInsetBottom();
+        updateMaxHeadsUpTranslation();
+        return insets;
+    }
+
+    private void updateMaxHeadsUpTranslation() {
+        mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight);
+    }
+
     @Override
     public void onRtlPropertiesChanged(int layoutDirection) {
         if (layoutDirection != mOldLayoutDirection) {
@@ -64,10 +2681,328 @@
     }
 
     @Override
+    public void onClick(View v) {
+        onQsExpansionStarted();
+        if (mQsExpanded) {
+            flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
+                    true /* isClick */);
+        } else if (mQsExpansionEnabled) {
+            mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
+            flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
+                    true /* isClick */);
+        }
+    }
+
+    @Override
+    public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
+        boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage;
+        mIsLaunchTransitionRunning = true;
+        mLaunchAnimationEndRunnable = null;
+        float displayDensity = mStatusBar.getDisplayDensity();
+        int lengthDp = Math.abs((int) (translation / displayDensity));
+        int velocityDp = Math.abs((int) (vel / displayDensity));
+        if (start) {
+            mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_DIALER, lengthDp, velocityDp);
+
+            mFalsingManager.onLeftAffordanceOn();
+            if (mFalsingManager.shouldEnforceBouncer()) {
+                mStatusBar.executeRunnableDismissingKeyguard(new Runnable() {
+                    @Override
+                    public void run() {
+                        mKeyguardBottomArea.launchLeftAffordance();
+                    }
+                }, null, true /* dismissShade */, false /* afterKeyguardGone */,
+                        true /* deferred */);
+            } else {
+                mKeyguardBottomArea.launchLeftAffordance();
+            }
+        } else {
+            if (KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE.equals(
+                    mLastCameraLaunchSource)) {
+                mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_CAMERA, lengthDp, velocityDp);
+            }
+            mFalsingManager.onCameraOn();
+            if (mFalsingManager.shouldEnforceBouncer()) {
+                mStatusBar.executeRunnableDismissingKeyguard(new Runnable() {
+                    @Override
+                    public void run() {
+                        mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
+                    }
+                }, null, true /* dismissShade */, false /* afterKeyguardGone */,
+                    true /* deferred */);
+            }
+            else {
+                mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
+            }
+        }
+        mStatusBar.startLaunchTransitionTimeout();
+        mBlockTouches = true;
+    }
+
+    @Override
+    public void onAnimationToSideEnded() {
+        mIsLaunchTransitionRunning = false;
+        mIsLaunchTransitionFinished = true;
+        if (mLaunchAnimationEndRunnable != null) {
+            mLaunchAnimationEndRunnable.run();
+            mLaunchAnimationEndRunnable = null;
+        }
+        mStatusBar.readyForKeyguardDone();
+    }
+
+    @Override
+    protected void startUnlockHintAnimation() {
+        if (mPowerManager.isPowerSaveMode()) {
+            onUnlockHintStarted();
+            onUnlockHintFinished();
+            return;
+        }
+        super.startUnlockHintAnimation();
+    }
+
+    @Override
+    public float getMaxTranslationDistance() {
+        return (float) Math.hypot(getWidth(), getHeight());
+    }
+
+    @Override
+    public void onSwipingStarted(boolean rightIcon) {
+        mFalsingManager.onAffordanceSwipingStarted(rightIcon);
+        boolean camera = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon
+                : rightIcon;
+        if (camera) {
+            mKeyguardBottomArea.bindCameraPrewarmService();
+        }
+        requestDisallowInterceptTouchEvent(true);
+        mOnlyAffordanceInThisMotion = true;
+        mQsTracking = false;
+    }
+
+    @Override
+    public void onSwipingAborted() {
+        mFalsingManager.onAffordanceSwipingAborted();
+        mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
+    }
+
+    @Override
+    public void onIconClicked(boolean rightIcon) {
+        if (mHintAnimationRunning) {
+            return;
+        }
+        mHintAnimationRunning = true;
+        mAffordanceHelper.startHintAnimation(rightIcon, new Runnable() {
+            @Override
+            public void run() {
+                mHintAnimationRunning = false;
+                mStatusBar.onHintFinished();
+            }
+        });
+        rightIcon = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon : rightIcon;
+        if (rightIcon) {
+            mStatusBar.onCameraHintStarted();
+        } else {
+            if (mKeyguardBottomArea.isLeftVoiceAssist()) {
+                mStatusBar.onVoiceAssistHintStarted();
+            } else {
+                mStatusBar.onPhoneHintStarted();
+            }
+        }
+    }
+
+    @Override
+    protected void onUnlockHintFinished() {
+        super.onUnlockHintFinished();
+        mNotificationStackScroller.setUnlockHintRunning(false);
+    }
+
+    @Override
+    protected void onUnlockHintStarted() {
+        super.onUnlockHintStarted();
+        mNotificationStackScroller.setUnlockHintRunning(true);
+    }
+
+    @Override
+    public KeyguardAffordanceView getLeftIcon() {
+        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
+                ? mKeyguardBottomArea.getRightView()
+                : mKeyguardBottomArea.getLeftView();
+    }
+
+    @Override
+    public KeyguardAffordanceView getRightIcon() {
+        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
+                ? mKeyguardBottomArea.getLeftView()
+                : mKeyguardBottomArea.getRightView();
+    }
+
+    @Override
+    public View getLeftPreview() {
+        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
+                ? mKeyguardBottomArea.getRightPreview()
+                : mKeyguardBottomArea.getLeftPreview();
+    }
+
+    @Override
+    public View getRightPreview() {
+        return getLayoutDirection() == LAYOUT_DIRECTION_RTL
+                ? mKeyguardBottomArea.getLeftPreview()
+                : mKeyguardBottomArea.getRightPreview();
+    }
+
+    @Override
+    public float getAffordanceFalsingFactor() {
+        return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+    }
+
+    @Override
+    public boolean needsAntiFalsing() {
+        return mBarState == StatusBarState.KEYGUARD;
+    }
+
+    @Override
+    protected float getPeekHeight() {
+        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
+            return mNotificationStackScroller.getPeekHeight();
+        } else {
+            return mQsMinExpansionHeight;
+        }
+    }
+
+    @Override
+    protected boolean shouldUseDismissingAnimation() {
+        return mBarState != StatusBarState.SHADE
+                && (mKeyguardStateController.canDismissLockScreen() || !isTracking());
+    }
+
+    @Override
+    protected boolean fullyExpandedClearAllVisible() {
+        return mNotificationStackScroller.isFooterViewNotGone()
+                && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate;
+    }
+
+    @Override
+    protected boolean isClearAllVisible() {
+        return mNotificationStackScroller.isFooterViewContentVisible();
+    }
+
+    @Override
+    protected int getClearAllHeight() {
+        return mNotificationStackScroller.getFooterViewHeight();
+    }
+
+    @Override
+    protected boolean isTrackingBlocked() {
+        return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
+    }
+
+    public boolean isQsExpanded() {
+        return mQsExpanded;
+    }
+
+    public boolean isQsDetailShowing() {
+        return mQs.isShowingDetail();
+    }
+
+    public void closeQsDetail() {
+        mQs.closeDetail();
+    }
+
+    @Override
     public boolean shouldDelayChildPressedState() {
         return true;
     }
 
+    public boolean isLaunchTransitionFinished() {
+        return mIsLaunchTransitionFinished;
+    }
+
+    public boolean isLaunchTransitionRunning() {
+        return mIsLaunchTransitionRunning;
+    }
+
+    public void setLaunchTransitionEndRunnable(Runnable r) {
+        mLaunchAnimationEndRunnable = r;
+    }
+
+    public void setEmptyDragAmount(float amount) {
+        mEmptyDragAmount = amount * 0.2f;
+        positionClockAndNotifications();
+    }
+
+    private void updateDozingVisibilities(boolean animate) {
+        mKeyguardBottomArea.setDozing(mDozing, animate);
+        if (!mDozing && animate) {
+            animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+        }
+    }
+
+    @Override
+    public boolean isDozing() {
+        return mDozing;
+    }
+
+    public void showEmptyShadeView(boolean emptyShadeViewVisible) {
+        mShowEmptyShadeView = emptyShadeViewVisible;
+        updateEmptyShadeView();
+    }
+
+    private void updateEmptyShadeView() {
+        // Hide "No notifications" in QS.
+        mNotificationStackScroller.updateEmptyShadeView(mShowEmptyShadeView && !mQsExpanded);
+    }
+
+    public void setQsScrimEnabled(boolean qsScrimEnabled) {
+        boolean changed = mQsScrimEnabled != qsScrimEnabled;
+        mQsScrimEnabled = qsScrimEnabled;
+        if (changed) {
+            updateQsState();
+        }
+    }
+
+    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
+        mKeyguardUserSwitcher = keyguardUserSwitcher;
+    }
+
+    public void onScreenTurningOn() {
+        mKeyguardStatusView.dozeTimeTick();
+    }
+
+    @Override
+    public void onEmptySpaceClicked(float x, float y) {
+        onEmptySpaceClick(x);
+    }
+
+    @Override
+    protected boolean onMiddleClicked() {
+        switch (mBarState) {
+            case StatusBarState.KEYGUARD:
+                if (!mDozingOnDown) {
+                    if (mKeyguardBypassController.getBypassEnabled()) {
+                        mUpdateMonitor.requestFaceAuth();
+                    } else {
+                        mLockscreenGestureLogger.write(
+                                MetricsEvent.ACTION_LS_HINT,
+                                0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+                        startUnlockHintAnimation();
+                    }
+                }
+                return true;
+            case StatusBarState.SHADE_LOCKED:
+                if (!mQsExpanded) {
+                    mStatusBarStateController.setState(StatusBarState.KEYGUARD);
+                }
+                return true;
+            case StatusBarState.SHADE:
+
+                // This gets called in the middle of the touch handling, where the state is still
+                // that we are tracking the panel. Collapse the panel after this is done.
+                post(mPostCollapseRunnable);
+                return false;
+            default:
+                return true;
+        }
+    }
+
     @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
@@ -76,18 +3011,250 @@
         }
     }
 
-    float getCurrentPanelAlpha() {
+    public float getCurrentPanelAlpha() {
         return mCurrentPanelAlpha;
     }
 
-    void setPanelAlphaInternal(float alpha) {
+    public boolean setPanelAlpha(int alpha, boolean animate) {
+        if (mPanelAlpha != alpha) {
+            mPanelAlpha = alpha;
+            PropertyAnimator.setProperty(this, PANEL_ALPHA, alpha,
+                    alpha == 255 ? PANEL_ALPHA_IN_PROPERTIES : PANEL_ALPHA_OUT_PROPERTIES, animate);
+            return true;
+        }
+        return false;
+    }
+
+    public void setPanelAlphaInternal(float alpha) {
         mCurrentPanelAlpha = (int) alpha;
         mAlphaPaint.setARGB(mCurrentPanelAlpha, 255, 255, 255);
         invalidate();
     }
 
-    public void setDozing(boolean dozing) {
-        mDozing = dozing;
+    public void setPanelAlphaEndAction(Runnable r) {
+        mPanelAlphaEndAction = r;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (DEBUG) {
+            Paint p = new Paint();
+            p.setColor(Color.RED);
+            p.setStrokeWidth(2);
+            p.setStyle(Paint.Style.STROKE);
+            canvas.drawLine(0, getMaxPanelHeight(), getWidth(), getMaxPanelHeight(), p);
+            p.setColor(Color.BLUE);
+            canvas.drawLine(0, getExpandedHeight(), getWidth(), getExpandedHeight(), p);
+            p.setColor(Color.GREEN);
+            canvas.drawLine(0, calculatePanelHeightQsExpanded(), getWidth(),
+                    calculatePanelHeightQsExpanded(), p);
+            p.setColor(Color.YELLOW);
+            canvas.drawLine(0, calculatePanelHeightShade(), getWidth(),
+                    calculatePanelHeightShade(), p);
+            p.setColor(Color.MAGENTA);
+            canvas.drawLine(0, calculateQsTopPadding(), getWidth(),
+                    calculateQsTopPadding(), p);
+            p.setColor(Color.CYAN);
+            canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, getWidth(),
+                    mNotificationStackScroller.getTopPadding(), p);
+            p.setColor(Color.GRAY);
+            canvas.drawLine(0, mClockPositionResult.clockY, getWidth(),
+                    mClockPositionResult.clockY, p);
+        }
+    }
+
+    @Override
+    public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
+        mNotificationStackScroller.setInHeadsUpPinnedMode(inPinnedMode);
+        if (inPinnedMode) {
+            mHeadsUpExistenceChangedRunnable.run();
+            updateNotificationTranslucency();
+        } else {
+            setHeadsUpAnimatingAway(true);
+            mNotificationStackScroller.runAfterAnimationFinished(
+                    mHeadsUpExistenceChangedRunnable);
+        }
+        updateGestureExclusionRect();
+        mHeadsUpPinnedMode = inPinnedMode;
+        updateHeadsUpVisibility();
+        updateKeyguardStatusBarForHeadsUp();
+    }
+
+    private void updateKeyguardStatusBarForHeadsUp() {
+        boolean showingKeyguardHeadsUp = mKeyguardShowing
+                && mHeadsUpAppearanceController.shouldBeVisible();
+        if (mShowingKeyguardHeadsUp != showingKeyguardHeadsUp) {
+            mShowingKeyguardHeadsUp = showingKeyguardHeadsUp;
+            if (mKeyguardShowing) {
+                PropertyAnimator.setProperty(this, KEYGUARD_HEADS_UP_SHOWING_AMOUNT,
+                        showingKeyguardHeadsUp ? 1.0f : 0.0f, KEYGUARD_HUN_PROPERTIES,
+                        true /* animate */);
+            } else {
+                PropertyAnimator.applyImmediately(this, KEYGUARD_HEADS_UP_SHOWING_AMOUNT, 0.0f);
+            }
+        }
+    }
+
+    private void setKeyguardHeadsUpShowingAmount(float amount) {
+        mKeyguardHeadsUpShowingAmount = amount;
+        updateHeaderKeyguardAlpha();
+    }
+
+    private float getKeyguardHeadsUpShowingAmount() {
+        return mKeyguardHeadsUpShowingAmount;
+    }
+
+    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+        mHeadsUpAnimatingAway = headsUpAnimatingAway;
+        mNotificationStackScroller.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+        updateHeadsUpVisibility();
+    }
+
+    private void updateHeadsUpVisibility() {
+        ((PhoneStatusBarView) mBar).setHeadsUpVisible(mHeadsUpAnimatingAway || mHeadsUpPinnedMode);
+    }
+
+    @Override
+    public void onHeadsUpPinned(NotificationEntry entry) {
+        if (!isOnKeyguard()) {
+            mNotificationStackScroller.generateHeadsUpAnimation(entry.getHeadsUpAnimationView(),
+                    true);
+        }
+    }
+
+    @Override
+    public void onHeadsUpUnPinned(NotificationEntry entry) {
+
+        // When we're unpinning the notification via active edge they remain heads-upped,
+        // we need to make sure that an animation happens in this case, otherwise the notification
+        // will stick to the top without any interaction.
+        if (isFullyCollapsed() && entry.isRowHeadsUp() && !isOnKeyguard()) {
+            mNotificationStackScroller.generateHeadsUpAnimation(
+                    entry.getHeadsUpAnimationView(), false);
+            entry.setHeadsUpIsVisible();
+        }
+    }
+
+    @Override
+    public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
+        mNotificationStackScroller.generateHeadsUpAnimation(entry, isHeadsUp);
+    }
+
+    @Override
+    public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
+        super.setHeadsUpManager(headsUpManager);
+        mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
+                mNotificationStackScroller.getHeadsUpCallback(), this);
+    }
+
+    public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) {
+        if (pickedChild != null) {
+            notifyListenersTrackingHeadsUp(pickedChild);
+            mExpandingFromHeadsUp = true;
+        }
+        // otherwise we update the state when the expansion is finished
+    }
+
+    @Override
+    protected void onClosingFinished() {
+        super.onClosingFinished();
+        resetHorizontalPanelPosition();
+        setClosingWithAlphaFadeout(false);
+    }
+
+    private void setClosingWithAlphaFadeout(boolean closing) {
+        mClosingWithAlphaFadeOut = closing;
+        mNotificationStackScroller.forceNoOverlappingRendering(closing);
+    }
+
+    /**
+     * Updates the vertical position of the panel so it is positioned closer to the touch
+     * responsible for opening the panel.
+     *
+     * @param x the x-coordinate the touch event
+     */
+    protected void updateVerticalPanelPosition(float x) {
+        if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) {
+            resetHorizontalPanelPosition();
+            return;
+        }
+        float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2;
+        float rightMost = getWidth() - mPositionMinSideMargin
+                - mNotificationStackScroller.getWidth() / 2;
+        if (Math.abs(x - getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) {
+            x = getWidth() / 2;
+        }
+        x = Math.min(rightMost, Math.max(leftMost, x));
+        float center =
+                mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2;
+        setHorizontalPanelTranslation(x - center);
+    }
+
+    private void resetHorizontalPanelPosition() {
+        setHorizontalPanelTranslation(0f);
+    }
+
+    protected void setHorizontalPanelTranslation(float translation) {
+        mNotificationStackScroller.setTranslationX(translation);
+        mQsFrame.setTranslationX(translation);
+        int size = mVerticalTranslationListener.size();
+        for (int i = 0; i < size; i++) {
+            mVerticalTranslationListener.get(i).run();
+        }
+    }
+
+    protected void updateExpandedHeight(float expandedHeight) {
+        if (mTracking) {
+            mNotificationStackScroller.setExpandingVelocity(getCurrentExpandVelocity());
+        }
+        if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()) {
+            // The expandedHeight is always the full panel Height when bypassing
+            expandedHeight = getMaxPanelHeightNonBypass();
+        }
+        mNotificationStackScroller.setExpandedHeight(expandedHeight);
+        updateKeyguardBottomAreaAlpha();
+        updateBigClockAlpha();
+        updateStatusBarIcons();
+    }
+
+    /**
+     * @return whether the notifications are displayed full width and don't have any margins on
+     *         the side.
+     */
+    public boolean isFullWidth() {
+        return mIsFullWidth;
+    }
+
+    private void updateStatusBarIcons() {
+        boolean showIconsWhenExpanded = (isPanelVisibleBecauseOfHeadsUp() || isFullWidth())
+                && getExpandedHeight() < getOpeningHeight();
+        if (showIconsWhenExpanded && mNoVisibleNotifications && isOnKeyguard()) {
+            showIconsWhenExpanded = false;
+        }
+        if (showIconsWhenExpanded != mShowIconsWhenExpanded) {
+            mShowIconsWhenExpanded = showIconsWhenExpanded;
+            mCommandQueue.recomputeDisableFlags(mDisplayId, false);
+        }
+    }
+
+    private boolean isOnKeyguard() {
+        return mBarState == StatusBarState.KEYGUARD;
+    }
+
+    public void setPanelScrimMinFraction(float minFraction) {
+        mBar.panelScrimMinFractionChanged(minFraction);
+    }
+
+    public void clearNotificationEffects() {
+        mStatusBar.clearNotificationEffects();
+    }
+
+    @Override
+    protected boolean isPanelVisibleBecauseOfHeadsUp() {
+        return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
+                && mBarState == StatusBarState.SHADE;
     }
 
     @Override
@@ -95,4 +3262,382 @@
         return !mDozing;
     }
 
+    public void launchCamera(boolean animate, int source) {
+        if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) {
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
+        } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
+        } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER) {
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER;
+        } else {
+
+            // Default.
+            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
+        }
+
+        // If we are launching it when we are occluded already we don't want it to animate,
+        // nor setting these flags, since the occluded state doesn't change anymore, hence it's
+        // never reset.
+        if (!isFullyCollapsed()) {
+            setLaunchingAffordance(true);
+        } else {
+            animate = false;
+        }
+        mAffordanceHasPreview = mKeyguardBottomArea.getRightPreview() != null;
+        mAffordanceHelper.launchAffordance(animate, getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+    }
+
+    public void onAffordanceLaunchEnded() {
+        setLaunchingAffordance(false);
+    }
+
+    /**
+     * Set whether we are currently launching an affordance. This is currently only set when
+     * launched via a camera gesture.
+     */
+    private void setLaunchingAffordance(boolean launchingAffordance) {
+        mLaunchingAffordance = launchingAffordance;
+        getLeftIcon().setLaunchingAffordance(launchingAffordance);
+        getRightIcon().setLaunchingAffordance(launchingAffordance);
+        mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
+        if (mAffordanceLaunchListener != null) {
+            mAffordanceLaunchListener.accept(launchingAffordance);
+        }
+    }
+
+    /**
+     * Return true when a bottom affordance is launching an occluded activity with a splash screen.
+     */
+    public boolean isLaunchingAffordanceWithPreview() {
+        return mLaunchingAffordance && mAffordanceHasPreview;
+    }
+
+    /**
+     * Whether the camera application can be launched for the camera launch gesture.
+     */
+    public boolean canCameraGestureBeLaunched() {
+        if (!mStatusBar.isCameraAllowedByAdmin()) {
+            return false;
+        }
+
+        ResolveInfo resolveInfo = mKeyguardBottomArea.resolveCameraIntent();
+        String packageToLaunch = (resolveInfo == null || resolveInfo.activityInfo == null)
+                ? null : resolveInfo.activityInfo.packageName;
+        return packageToLaunch != null &&
+                (mBarState != StatusBarState.SHADE || !isForegroundApp(packageToLaunch))
+                && !mAffordanceHelper.isSwipingInProgress();
+    }
+
+    /**
+     * Return true if the applications with the package name is running in foreground.
+     *
+     * @param pkgName application package name.
+     */
+    private boolean isForegroundApp(String pkgName) {
+        ActivityManager am = getContext().getSystemService(ActivityManager.class);
+        List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
+        return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
+    }
+
+    private void setGroupManager(NotificationGroupManager groupManager) {
+        mGroupManager = groupManager;
+    }
+
+    public boolean hideStatusBarIconsWhenExpanded() {
+        if (mLaunchingNotification) {
+            return mHideIconsDuringNotificationLaunch;
+        }
+        if (mHeadsUpAppearanceController != null
+                && mHeadsUpAppearanceController.shouldBeVisible()) {
+            return false;
+        }
+        return !isFullWidth() || !mShowIconsWhenExpanded;
+    }
+
+    private final FragmentListener mFragmentListener = new FragmentListener() {
+        @Override
+        public void onFragmentViewCreated(String tag, Fragment fragment) {
+            mQs = (QS) fragment;
+            mQs.setPanelView(NotificationPanelView.this);
+            mQs.setExpandClickListener(NotificationPanelView.this);
+            mQs.setHeaderClickable(mQsExpansionEnabled);
+            updateQSPulseExpansion();
+            mQs.setOverscrolling(mStackScrollerOverscrolling);
+
+            // recompute internal state when qspanel height changes
+            mQs.getView().addOnLayoutChangeListener(
+                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                        final int height = bottom - top;
+                        final int oldHeight = oldBottom - oldTop;
+                        if (height != oldHeight) {
+                            onQsHeightChanged();
+                        }
+                    });
+            mNotificationStackScroller.setQsContainer((ViewGroup) mQs.getView());
+            if (mQs instanceof QSFragment) {
+                mKeyguardStatusBar.setQSPanel(((QSFragment) mQs).getQsPanel());
+            }
+            updateQsExpansion();
+        }
+
+        @Override
+        public void onFragmentViewDestroyed(String tag, Fragment fragment) {
+            // Manual handling of fragment lifecycle is only required because this bridges
+            // non-fragment and fragment code. Once we are using a fragment for the notification
+            // panel, mQs will not need to be null cause it will be tied to the same lifecycle.
+            if (fragment == mQs) {
+                mQs = null;
+            }
+        }
+    };
+
+    @Override
+    public void setTouchAndAnimationDisabled(boolean disabled) {
+        super.setTouchAndAnimationDisabled(disabled);
+        if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
+            mAffordanceHelper.reset(false /* animate */);
+        }
+        mNotificationStackScroller.setAnimationsEnabled(!disabled);
+    }
+
+    /**
+     * Sets the dozing state.
+     *
+     * @param dozing {@code true} when dozing.
+     * @param animate if transition should be animated.
+     * @param wakeUpTouchLocation touch event location - if woken up by SLPI sensor.
+     */
+    public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) {
+        if (dozing == mDozing) return;
+        mDozing = dozing;
+        mNotificationStackScroller.setDozing(mDozing, animate, wakeUpTouchLocation);
+        mKeyguardBottomArea.setDozing(mDozing, animate);
+
+        if (dozing) {
+            mBottomAreaShadeAlphaAnimator.cancel();
+        }
+
+        if (mBarState == StatusBarState.KEYGUARD
+                || mBarState == StatusBarState.SHADE_LOCKED) {
+            updateDozingVisibilities(animate);
+        }
+
+        final float dozeAmount = dozing ? 1 : 0;
+        mStatusBarStateController.setDozeAmount(dozeAmount, animate);
+    }
+
+    @Override
+    public void onDozeAmountChanged(float linearAmount, float amount) {
+        mInterpolatedDarkAmount = amount;
+        mLinearDarkAmount = linearAmount;
+        mKeyguardStatusView.setDarkAmount(mInterpolatedDarkAmount);
+        mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
+        positionClockAndNotifications();
+    }
+
+    public void setPulsing(boolean pulsing) {
+        mPulsing = pulsing;
+        final boolean animatePulse = !mDozeParameters.getDisplayNeedsBlanking()
+                && mDozeParameters.getAlwaysOn();
+        if (animatePulse) {
+            mAnimateNextPositionUpdate = true;
+        }
+        // Do not animate the clock when waking up from a pulse.
+        // The height callback will take care of pushing the clock to the right position.
+        if (!mPulsing && !mDozing) {
+            mAnimateNextPositionUpdate = false;
+        }
+        mNotificationStackScroller.setPulsing(pulsing, animatePulse);
+        mKeyguardStatusView.setPulsing(pulsing);
+    }
+
+    public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) {
+        if (mAmbientIndicationBottomPadding != ambientIndicationBottomPadding) {
+            mAmbientIndicationBottomPadding = ambientIndicationBottomPadding;
+            mStatusBar.updateKeyguardMaxNotifications();
+        }
+    }
+
+    public void dozeTimeTick() {
+        mKeyguardBottomArea.dozeTimeTick();
+        mKeyguardStatusView.dozeTimeTick();
+        if (mInterpolatedDarkAmount > 0) {
+            positionClockAndNotifications();
+        }
+    }
+
+    public void setStatusAccessibilityImportance(int mode) {
+        mKeyguardStatusView.setImportantForAccessibility(mode);
+    }
+
+    /**
+     * TODO: this should be removed.
+     * It's not correct to pass this view forward because other classes will end up adding
+     * children to it. Theme will be out of sync.
+     *
+     * @return bottom area view
+     */
+    public KeyguardBottomAreaView getKeyguardBottomAreaView() {
+        return mKeyguardBottomArea;
+    }
+
+    public void setUserSetupComplete(boolean userSetupComplete) {
+        mUserSetupComplete = userSetupComplete;
+        mKeyguardBottomArea.setUserSetupComplete(userSetupComplete);
+    }
+
+    public void applyExpandAnimationParams(ExpandAnimationParameters params) {
+        mExpandOffset = params != null ? params.getTopChange() : 0;
+        updateQsExpansion();
+        if (params != null) {
+            boolean hideIcons = params.getProgress(
+                    ActivityLaunchAnimator.ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
+            if (hideIcons != mHideIconsDuringNotificationLaunch) {
+                mHideIconsDuringNotificationLaunch = hideIcons;
+                if (!hideIcons) {
+                    mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
+                }
+            }
+        }
+    }
+
+    public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
+        mTrackingHeadsUpListeners.add(listener);
+    }
+
+    public void removeTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
+        mTrackingHeadsUpListeners.remove(listener);
+    }
+
+    public void addVerticalTranslationListener(Runnable verticalTranslationListener) {
+        mVerticalTranslationListener.add(verticalTranslationListener);
+    }
+
+    public void removeVerticalTranslationListener(Runnable verticalTranslationListener) {
+        mVerticalTranslationListener.remove(verticalTranslationListener);
+    }
+
+    public void setHeadsUpAppearanceController(
+            HeadsUpAppearanceController headsUpAppearanceController) {
+        mHeadsUpAppearanceController = headsUpAppearanceController;
+    }
+
+    /**
+     * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
+     * security view of the bouncer.
+     */
+    public void onBouncerPreHideAnimation() {
+        setKeyguardStatusViewVisibility(mBarState, true /* keyguardFadingAway */,
+                false /* goingToFullShade */);
+    }
+
+    /**
+     * Do not let the user drag the shade up and down for the current touch session.
+     * This is necessary to avoid shade expansion while/after the bouncer is dismissed.
+     */
+    public void blockExpansionForCurrentTouch() {
+        mBlockingExpansionForCurrentTouch = mTracking;
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dump(fd, pw, args);
+        pw.println("    gestureExclusionRect: " + calculateGestureExclusionRect());
+        if (mKeyguardStatusBar != null) {
+            mKeyguardStatusBar.dump(fd, pw, args);
+        }
+        if (mKeyguardStatusView != null) {
+            mKeyguardStatusView.dump(fd, pw, args);
+        }
+    }
+
+    public boolean hasActiveClearableNotifications() {
+        return mNotificationStackScroller.hasActiveClearableNotifications(ROWS_ALL);
+    }
+
+    @Override
+    public void onZenChanged(int zen) {
+        updateShowEmptyShadeView();
+    }
+
+    private void updateShowEmptyShadeView() {
+        boolean showEmptyShadeView =
+                mBarState != StatusBarState.KEYGUARD && !mEntryManager.hasActiveNotifications();
+        showEmptyShadeView(showEmptyShadeView);
+    }
+
+    public RemoteInputController.Delegate createRemoteInputDelegate() {
+        return mNotificationStackScroller.createDelegate();
+    }
+
+    public void updateNotificationViews() {
+        mNotificationStackScroller.updateSectionBoundaries();
+        mNotificationStackScroller.updateSpeedBumpIndex();
+        mNotificationStackScroller.updateFooter();
+        updateShowEmptyShadeView();
+        mNotificationStackScroller.updateIconAreaViews();
+    }
+
+    public void onUpdateRowStates() {
+        mNotificationStackScroller.onUpdateRowStates();
+    }
+
+    public boolean hasPulsingNotifications() {
+        return mNotificationStackScroller.hasPulsingNotifications();
+    }
+
+    public ActivatableNotificationView getActivatedChild() {
+        return mNotificationStackScroller.getActivatedChild();
+    }
+
+    public void setActivatedChild(ActivatableNotificationView o) {
+        mNotificationStackScroller.setActivatedChild(o);
+    }
+
+    public void runAfterAnimationFinished(Runnable r) {
+        mNotificationStackScroller.runAfterAnimationFinished(r);
+    }
+
+    public void setScrollingEnabled(boolean b) {
+        mNotificationStackScroller.setScrollingEnabled(b);
+    }
+
+    public void initDependencies(StatusBar statusBar, NotificationGroupManager groupManager,
+            NotificationShelf notificationShelf,
+            HeadsUpManagerPhone headsUpManager,
+            NotificationIconAreaController notificationIconAreaController,
+            ScrimController scrimController) {
+        setStatusBar(statusBar);
+        setGroupManager(mGroupManager);
+        mNotificationStackScroller.setNotificationPanel(this);
+        mNotificationStackScroller.setIconAreaController(notificationIconAreaController);
+        mNotificationStackScroller.setStatusBar(statusBar);
+        mNotificationStackScroller.setGroupManager(groupManager);
+        mNotificationStackScroller.setShelf(notificationShelf);
+        mNotificationStackScroller.setScrimController(scrimController);
+        updateShowEmptyShadeView();
+    }
+
+    public void showTransientIndication(int id) {
+        mKeyguardIndicationController.showTransientIndication(id);
+    }
+
+    @Override
+    public void onDynamicPrivacyChanged() {
+        // Do not request animation when pulsing or waking up, otherwise the clock wiill be out
+        // of sync with the notification panel.
+        if (mLinearDarkAmount != 0) {
+            return;
+        }
+        mAnimateNextPositionUpdate = true;
+    }
+
+    public void setOnReinflationListener(Runnable onReinflationListener) {
+        mOnReinflationListener = onReinflationListener;
+    }
+
+    public static boolean isQsSplitEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.QS_SPLIT_ENABLED, false);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
deleted file mode 100644
index bbde25b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ /dev/null
@@ -1,3733 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.app.ActivityManager;
-import android.app.Fragment;
-import android.app.StatusBarManager;
-import android.content.Context;
-import android.content.pm.ResolveInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.drawable.Drawable;
-import android.hardware.biometrics.BiometricSourceType;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.provider.DeviceConfig;
-import android.provider.Settings;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.LatencyTracker;
-import com.android.keyguard.KeyguardClockSwitch;
-import com.android.keyguard.KeyguardStatusView;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.DisplayId;
-import com.android.systemui.doze.DozeLog;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.HomeControlsPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.qs.QSFragment;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FlingAnimationUtils;
-import com.android.systemui.statusbar.GestureRecorder;
-import com.android.systemui.statusbar.KeyguardAffordanceView;
-import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.RemoteInputController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
-import com.android.systemui.statusbar.notification.AnimatableProperty;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.InjectionInflationController;
-import com.android.systemui.util.Utils;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-import javax.inject.Inject;
-
-@StatusBarComponent.StatusBarScope
-public class NotificationPanelViewController extends PanelViewController {
-
-    private static final boolean DEBUG = false;
-
-    /**
-     * Fling expanding QS.
-     */
-    private static final int FLING_EXPAND = 0;
-
-    /**
-     * Fling collapsing QS, potentially stopping when QS becomes QQS.
-     */
-    private static final int FLING_COLLAPSE = 1;
-
-    /**
-     * Fling until QS is completely hidden.
-     */
-    private static final int FLING_HIDE = 2;
-    private final DozeParameters mDozeParameters;
-    private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener();
-    private final OnClickListener mOnClickListener = new OnClickListener();
-    private final OnOverscrollTopChangedListener
-            mOnOverscrollTopChangedListener =
-            new OnOverscrollTopChangedListener();
-    private final KeyguardAffordanceHelperCallback
-            mKeyguardAffordanceHelperCallback =
-            new KeyguardAffordanceHelperCallback();
-    private final OnEmptySpaceClickListener
-            mOnEmptySpaceClickListener =
-            new OnEmptySpaceClickListener();
-    private final MyOnHeadsUpChangedListener
-            mOnHeadsUpChangedListener =
-            new MyOnHeadsUpChangedListener();
-    private final HeightListener mHeightListener = new HeightListener();
-    private final ZenModeControllerCallback
-            mZenModeControllerCallback =
-            new ZenModeControllerCallback();
-    private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
-    private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener();
-    private final ExpansionCallback mExpansionCallback = new ExpansionCallback();
-    private final NotificationPanelView mView;
-    private final MetricsLogger mMetricsLogger;
-    private final ActivityManager mActivityManager;
-    private final ZenModeController mZenModeController;
-    private final ConfigurationController mConfigurationController;
-    private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
-
-    private double mQqsSplitFraction;
-
-    // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
-    // changed.
-    private static final int CAP_HEIGHT = 1456;
-    private static final int FONT_HEIGHT = 2163;
-
-    /**
-     * Maximum time before which we will expand the panel even for slow motions when getting a
-     * touch passed over from launcher.
-     */
-    private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300;
-
-    private static final String COUNTER_PANEL_OPEN = "panel_open";
-    private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
-    private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
-
-    private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
-    private static final Rect EMPTY_RECT = new Rect();
-
-    private static final AnimationProperties
-            CLOCK_ANIMATION_PROPERTIES =
-            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-    private final AnimatableProperty KEYGUARD_HEADS_UP_SHOWING_AMOUNT = AnimatableProperty.from(
-            "KEYGUARD_HEADS_UP_SHOWING_AMOUNT",
-            (notificationPanelView, aFloat) -> setKeyguardHeadsUpShowingAmount(aFloat),
-            (Function<NotificationPanelView, Float>) notificationPanelView ->
-                    getKeyguardHeadsUpShowingAmount(),
-            R.id.keyguard_hun_animator_tag, R.id.keyguard_hun_animator_end_tag,
-            R.id.keyguard_hun_animator_start_tag);
-    private static final AnimationProperties
-            KEYGUARD_HUN_PROPERTIES =
-            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-    @VisibleForTesting
-    final KeyguardUpdateMonitorCallback
-            mKeyguardUpdateCallback =
-            new KeyguardUpdateMonitorCallback() {
-
-                @Override
-                public void onBiometricAuthenticated(int userId,
-                        BiometricSourceType biometricSourceType) {
-                    if (mFirstBypassAttempt && mUpdateMonitor.isUnlockingWithBiometricAllowed()) {
-                        mDelayShowingKeyguardStatusBar = true;
-                    }
-                }
-
-                @Override
-                public void onBiometricRunningStateChanged(boolean running,
-                        BiometricSourceType biometricSourceType) {
-                    boolean
-                            keyguardOrShadeLocked =
-                            mBarState == StatusBarState.KEYGUARD
-                                    || mBarState == StatusBarState.SHADE_LOCKED;
-                    if (!running && mFirstBypassAttempt && keyguardOrShadeLocked && !mDozing
-                            && !mDelayShowingKeyguardStatusBar) {
-                        mFirstBypassAttempt = false;
-                        animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                    }
-                }
-
-                @Override
-                public void onFinishedGoingToSleep(int why) {
-                    mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
-                    mDelayShowingKeyguardStatusBar = false;
-                }
-            };
-
-    private final InjectionInflationController mInjectionInflationController;
-    private final PowerManager mPowerManager;
-    private final AccessibilityManager mAccessibilityManager;
-    private final NotificationWakeUpCoordinator mWakeUpCoordinator;
-    private final PulseExpansionHandler mPulseExpansionHandler;
-    private final KeyguardBypassController mKeyguardBypassController;
-    private final KeyguardUpdateMonitor mUpdateMonitor;
-
-    private KeyguardAffordanceHelper mAffordanceHelper;
-    private KeyguardUserSwitcher mKeyguardUserSwitcher;
-    private KeyguardStatusBarView mKeyguardStatusBar;
-    private ViewGroup mBigClockContainer;
-    private QS mQs;
-    private FrameLayout mQsFrame;
-    private KeyguardStatusView mKeyguardStatusView;
-    private View mQsNavbarScrim;
-    private NotificationsQuickSettingsContainer mNotificationContainerParent;
-    private NotificationStackScrollLayout mNotificationStackScroller;
-    private FrameLayout mHomeControlsLayout;
-    private boolean mAnimateNextPositionUpdate;
-
-    private int mTrackingPointer;
-    private VelocityTracker mQsVelocityTracker;
-    private boolean mQsTracking;
-
-    /**
-     * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
-     * the expansion for quick settings.
-     */
-    private boolean mConflictingQsExpansionGesture;
-
-    private boolean mPanelExpanded;
-    private boolean mQsExpanded;
-    private boolean mQsExpandedWhenExpandingStarted;
-    private boolean mQsFullyExpanded;
-    private boolean mKeyguardShowing;
-    private boolean mDozing;
-    private boolean mDozingOnDown;
-    private int mBarState;
-    private float mInitialHeightOnTouch;
-    private float mInitialTouchX;
-    private float mInitialTouchY;
-    private float mQsExpansionHeight;
-    private int mQsMinExpansionHeight;
-    private int mQsMaxExpansionHeight;
-    private int mQsPeekHeight;
-    private boolean mStackScrollerOverscrolling;
-    private boolean mQsExpansionFromOverscroll;
-    private float mLastOverscroll;
-    private boolean mQsExpansionEnabled = true;
-    private ValueAnimator mQsExpansionAnimator;
-    private FlingAnimationUtils mFlingAnimationUtils;
-    private int mStatusBarMinHeight;
-    private int mNotificationsHeaderCollideDistance;
-    private float mEmptyDragAmount;
-    private float mDownX;
-    private float mDownY;
-
-    private final KeyguardClockPositionAlgorithm
-            mClockPositionAlgorithm =
-            new KeyguardClockPositionAlgorithm();
-    private final KeyguardClockPositionAlgorithm.Result
-            mClockPositionResult =
-            new KeyguardClockPositionAlgorithm.Result();
-    private boolean mIsExpanding;
-
-    private boolean mBlockTouches;
-    // Used for two finger gesture as well as accessibility shortcut to QS.
-    private boolean mQsExpandImmediate;
-    private boolean mTwoFingerQsExpandPossible;
-
-    /**
-     * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
-     * need to take this into account in our panel height calculation.
-     */
-    private boolean mQsAnimatorExpand;
-    private boolean mIsLaunchTransitionFinished;
-    private boolean mIsLaunchTransitionRunning;
-    private Runnable mLaunchAnimationEndRunnable;
-    private boolean mOnlyAffordanceInThisMotion;
-    private boolean mKeyguardStatusViewAnimating;
-    private ValueAnimator mQsSizeChangeAnimator;
-
-    private boolean mShowEmptyShadeView;
-
-    private boolean mQsScrimEnabled = true;
-    private boolean mQsTouchAboveFalsingThreshold;
-    private int mQsFalsingThreshold;
-
-    private float mKeyguardStatusBarAnimateAlpha = 1f;
-    private HeadsUpTouchHelper mHeadsUpTouchHelper;
-    private boolean mListenForHeadsUp;
-    private int mNavigationBarBottomHeight;
-    private boolean mExpandingFromHeadsUp;
-    private boolean mCollapsedOnDown;
-    private int mPositionMinSideMargin;
-    private int mLastOrientation = -1;
-    private boolean mClosingWithAlphaFadeOut;
-    private boolean mHeadsUpAnimatingAway;
-    private boolean mLaunchingAffordance;
-    private boolean mAffordanceHasPreview;
-    private FalsingManager mFalsingManager;
-    private String mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
-
-    private Runnable mHeadsUpExistenceChangedRunnable = () -> {
-        setHeadsUpAnimatingAway(false);
-        notifyBarPanelExpansionChanged();
-    };
-    private NotificationGroupManager mGroupManager;
-    private boolean mShowIconsWhenExpanded;
-    private int mIndicationBottomPadding;
-    private int mAmbientIndicationBottomPadding;
-    private boolean mIsFullWidth;
-    private boolean mBlockingExpansionForCurrentTouch;
-
-    /**
-     * Following variables maintain state of events when input focus transfer may occur.
-     */
-    private boolean mExpectingSynthesizedDown; // expecting to see synthesized DOWN event
-    private boolean mLastEventSynthesizedDown; // last event was synthesized DOWN event
-
-    /**
-     * Current dark amount that follows regular interpolation curve of animation.
-     */
-    private float mInterpolatedDarkAmount;
-
-    /**
-     * Dark amount that animates from 0 to 1 or vice-versa in linear manner, even if the
-     * interpolation curve is different.
-     */
-    private float mLinearDarkAmount;
-
-    private boolean mPulsing;
-    private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
-    private boolean mUserSetupComplete;
-    private int mQsNotificationTopPadding;
-    private float mExpandOffset;
-    private boolean mHideIconsDuringNotificationLaunch = true;
-    private int mStackScrollerMeasuringPass;
-    private ArrayList<Consumer<ExpandableNotificationRow>>
-            mTrackingHeadsUpListeners =
-            new ArrayList<>();
-    private ArrayList<Runnable> mVerticalTranslationListener = new ArrayList<>();
-    private HeadsUpAppearanceController mHeadsUpAppearanceController;
-
-    private int mPanelAlpha;
-    private Runnable mPanelAlphaEndAction;
-    private float mBottomAreaShadeAlpha;
-    private final ValueAnimator mBottomAreaShadeAlphaAnimator;
-    private AnimatorListenerAdapter mAnimatorListenerAdapter = new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            if (mPanelAlphaEndAction != null) {
-                mPanelAlphaEndAction.run();
-            }
-        }
-    };
-    private final AnimatableProperty mPanelAlphaAnimator = AnimatableProperty.from("panelAlpha",
-            NotificationPanelView::setPanelAlphaInternal,
-            NotificationPanelView::getCurrentPanelAlpha,
-            R.id.panel_alpha_animator_tag, R.id.panel_alpha_animator_start_tag,
-            R.id.panel_alpha_animator_end_tag);
-    private final AnimationProperties mPanelAlphaOutPropertiesAnimator =
-            new AnimationProperties().setDuration(150).setCustomInterpolator(
-                    mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_OUT);
-    private final AnimationProperties mPanelAlphaInPropertiesAnimator =
-            new AnimationProperties().setDuration(200).setAnimationFinishListener(
-                    mAnimatorListenerAdapter).setCustomInterpolator(
-                    mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_IN);
-    private final NotificationEntryManager mEntryManager;
-
-    private final CommandQueue mCommandQueue;
-    private final NotificationLockscreenUserManager mLockscreenUserManager;
-    private final ShadeController mShadeController;
-    private int mDisplayId;
-
-    /**
-     * Cache the resource id of the theme to avoid unnecessary work in onThemeChanged.
-     *
-     * onThemeChanged is forced when the theme might not have changed. So, to avoid unncessary
-     * work, check the current id with the cached id.
-     */
-    private int mThemeResId;
-    private KeyguardIndicationController mKeyguardIndicationController;
-    private Consumer<Boolean> mAffordanceLaunchListener;
-    private int mShelfHeight;
-    private Runnable mOnReinflationListener;
-    private int mDarkIconSize;
-    private int mHeadsUpInset;
-    private boolean mHeadsUpPinnedMode;
-    private float mKeyguardHeadsUpShowingAmount = 0.0f;
-    private boolean mShowingKeyguardHeadsUp;
-    private boolean mAllowExpandForSmallExpansion;
-    private Runnable mExpandAfterLayoutRunnable;
-
-    /**
-     * If face auth with bypass is running for the first time after you turn on the screen.
-     * (From aod or screen off)
-     */
-    private boolean mFirstBypassAttempt;
-    /**
-     * If auth happens successfully during {@code mFirstBypassAttempt}, and we should wait until
-     * the keyguard is dismissed to show the status bar.
-     */
-    private boolean mDelayShowingKeyguardStatusBar;
-
-    private PluginManager mPluginManager;
-    private FrameLayout mPluginFrame;
-    private NPVPluginManager mNPVPluginManager;
-
-    @Inject
-    public NotificationPanelViewController(NotificationPanelView view,
-            InjectionInflationController injectionInflationController,
-            NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler,
-            DynamicPrivacyController dynamicPrivacyController,
-            KeyguardBypassController bypassController, FalsingManager falsingManager,
-            PluginManager pluginManager, ShadeController shadeController,
-            NotificationLockscreenUserManager notificationLockscreenUserManager,
-            NotificationEntryManager notificationEntryManager,
-            KeyguardStateController keyguardStateController,
-            StatusBarStateController statusBarStateController, DozeLog dozeLog,
-            DozeParameters dozeParameters, CommandQueue commandQueue, VibratorHelper vibratorHelper,
-            LatencyTracker latencyTracker, PowerManager powerManager,
-            AccessibilityManager accessibilityManager, @DisplayId int displayId,
-            KeyguardUpdateMonitor keyguardUpdateMonitor, MetricsLogger metricsLogger,
-            ActivityManager activityManager, ZenModeController zenModeController,
-            ConfigurationController configurationController,
-            FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
-        super(view, falsingManager, dozeLog, keyguardStateController,
-                (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
-                latencyTracker, flingAnimationUtilsBuilder);
-        mView = view;
-        mMetricsLogger = metricsLogger;
-        mActivityManager = activityManager;
-        mZenModeController = zenModeController;
-        mConfigurationController = configurationController;
-        mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
-        mView.setWillNotDraw(!DEBUG);
-        mInjectionInflationController = injectionInflationController;
-        mFalsingManager = falsingManager;
-        mPowerManager = powerManager;
-        mWakeUpCoordinator = coordinator;
-        mAccessibilityManager = accessibilityManager;
-        mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
-        setPanelAlpha(255, false /* animate */);
-        mCommandQueue = commandQueue;
-        mDisplayId = displayId;
-        mPulseExpansionHandler = pulseExpansionHandler;
-        mDozeParameters = dozeParameters;
-        pulseExpansionHandler.setPulseExpandAbortListener(() -> {
-            if (mQs != null) {
-                mQs.animateHeaderSlidingOut();
-            }
-        });
-        mThemeResId = mView.getContext().getThemeResId();
-        mKeyguardBypassController = bypassController;
-        mUpdateMonitor = keyguardUpdateMonitor;
-        mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
-        KeyguardStateController.Callback
-                keyguardMonitorCallback =
-                new KeyguardStateController.Callback() {
-                    @Override
-                    public void onKeyguardFadingAwayChanged() {
-                        if (!mKeyguardStateController.isKeyguardFadingAway()) {
-                            mFirstBypassAttempt = false;
-                            mDelayShowingKeyguardStatusBar = false;
-                        }
-                    }
-                };
-        mKeyguardStateController.addCallback(keyguardMonitorCallback);
-        DynamicPrivacyControlListener
-                dynamicPrivacyControlListener =
-                new DynamicPrivacyControlListener();
-        dynamicPrivacyController.addListener(dynamicPrivacyControlListener);
-
-        mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
-        mBottomAreaShadeAlphaAnimator.addUpdateListener(animation -> {
-            mBottomAreaShadeAlpha = (float) animation.getAnimatedValue();
-            updateKeyguardBottomAreaAlpha();
-        });
-        mBottomAreaShadeAlphaAnimator.setDuration(160);
-        mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT);
-        mPluginManager = pluginManager;
-        mShadeController = shadeController;
-        mLockscreenUserManager = notificationLockscreenUserManager;
-        mEntryManager = notificationEntryManager;
-
-        mView.setBackgroundColor(Color.TRANSPARENT);
-        OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener();
-        mView.addOnAttachStateChangeListener(onAttachStateChangeListener);
-        if (mView.isAttachedToWindow()) {
-            onAttachStateChangeListener.onViewAttachedToWindow(mView);
-        }
-
-        mView.setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener());
-
-        if (DEBUG) {
-            mView.getOverlay().add(new DebugDrawable());
-        }
-
-        onFinishInflate();
-    }
-
-    private void onFinishInflate() {
-        loadDimens();
-        mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
-        mKeyguardStatusView = mView.findViewById(R.id.keyguard_status_view);
-
-        KeyguardClockSwitch keyguardClockSwitch = mView.findViewById(R.id.keyguard_clock_container);
-        mBigClockContainer = mView.findViewById(R.id.big_clock_container);
-        keyguardClockSwitch.setBigClockContainer(mBigClockContainer);
-
-        mHomeControlsLayout = mView.findViewById(R.id.home_controls_layout);
-        mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
-        mNotificationStackScroller = mView.findViewById(R.id.notification_stack_scroller);
-        mNotificationStackScroller.setOnHeightChangedListener(mOnHeightChangedListener);
-        mNotificationStackScroller.setOverscrollTopChangedListener(mOnOverscrollTopChangedListener);
-        mNotificationStackScroller.setOnEmptySpaceClickListener(mOnEmptySpaceClickListener);
-        addTrackingHeadsUpListener(mNotificationStackScroller::setTrackingHeadsUp);
-        mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
-        mQsNavbarScrim = mView.findViewById(R.id.qs_navbar_scrim);
-        mLastOrientation = mResources.getConfiguration().orientation;
-        mPluginFrame = mView.findViewById(R.id.plugin_frame);
-        if (Settings.System.getInt(mView.getContext().getContentResolver(), "npv_plugin_flag", 0)
-                == 1) {
-            mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
-        }
-
-
-        initBottomArea();
-
-        mWakeUpCoordinator.setStackScroller(mNotificationStackScroller);
-        mQsFrame = mView.findViewById(R.id.qs_frame);
-        mPulseExpansionHandler.setUp(
-                mNotificationStackScroller, mExpansionCallback, mShadeController);
-        mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() {
-            @Override
-            public void onFullyHiddenChanged(boolean isFullyHidden) {
-                updateKeyguardStatusBarForHeadsUp();
-            }
-
-            @Override
-            public void onPulseExpansionChanged(boolean expandingChanged) {
-                if (mKeyguardBypassController.getBypassEnabled()) {
-                    // Position the notifications while dragging down while pulsing
-                    requestScrollerTopPaddingUpdate(false /* animate */);
-                    updateQSPulseExpansion();
-                }
-            }
-        });
-
-        mPluginManager.addPluginListener(new PluginListener<HomeControlsPlugin>() {
-
-            @Override
-            public void onPluginConnected(HomeControlsPlugin plugin, Context pluginContext) {
-                plugin.sendParentGroup(mHomeControlsLayout);
-            }
-
-            @Override
-            public void onPluginDisconnected(HomeControlsPlugin plugin) {
-
-            }
-        }, HomeControlsPlugin.class, false);
-    }
-
-    @Override
-    protected void loadDimens() {
-        super.loadDimens();
-        mFlingAnimationUtils = mFlingAnimationUtilsBuilder.reset()
-                .setMaxLengthSeconds(0.4f).build();
-        mStatusBarMinHeight = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_height);
-        mQsPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
-        mNotificationsHeaderCollideDistance = mResources.getDimensionPixelSize(
-                R.dimen.header_notifications_collide_distance);
-        mClockPositionAlgorithm.loadDimens(mResources);
-        mQsFalsingThreshold = mResources.getDimensionPixelSize(R.dimen.qs_falsing_threshold);
-        mPositionMinSideMargin = mResources.getDimensionPixelSize(
-                R.dimen.notification_panel_min_side_margin);
-        mIndicationBottomPadding = mResources.getDimensionPixelSize(
-                R.dimen.keyguard_indication_bottom_padding);
-        mQsNotificationTopPadding = mResources.getDimensionPixelSize(
-                R.dimen.qs_notification_padding);
-        mShelfHeight = mResources.getDimensionPixelSize(R.dimen.notification_shelf_height);
-        mDarkIconSize = mResources.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
-        int statusbarHeight = mResources.getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_height);
-        mHeadsUpInset = statusbarHeight + mResources.getDimensionPixelSize(
-                R.dimen.heads_up_status_bar_padding);
-        mQqsSplitFraction = ((float) mResources.getInteger(R.integer.qqs_split_fraction)) / (
-                mResources.getInteger(R.integer.qqs_split_fraction) + mResources.getInteger(
-                        R.integer.qs_split_fraction));
-    }
-
-    /**
-     * Returns if there's a custom clock being presented.
-     */
-    public boolean hasCustomClock() {
-        return mKeyguardStatusView.hasCustomClock();
-    }
-
-    private void setStatusBar(StatusBar bar) {
-        // TODO: this can be injected.
-        mStatusBar = bar;
-        mKeyguardBottomArea.setStatusBar(mStatusBar);
-    }
-    /**
-     * @see #launchCamera(boolean, int)
-     * @see #setLaunchingAffordance(boolean)
-     */
-    public void setLaunchAffordanceListener(Consumer<Boolean> listener) {
-        mAffordanceLaunchListener = listener;
-    }
-
-    public void updateResources() {
-        int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width);
-        int panelGravity = mResources.getInteger(R.integer.notification_panel_layout_gravity);
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mQsFrame.getLayoutParams();
-        if (lp.width != qsWidth || lp.gravity != panelGravity) {
-            lp.width = qsWidth;
-            lp.gravity = panelGravity;
-            mQsFrame.setLayoutParams(lp);
-        }
-
-        int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
-        lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
-        if (lp.width != panelWidth || lp.gravity != panelGravity) {
-            lp.width = panelWidth;
-            lp.gravity = panelGravity;
-            mNotificationStackScroller.setLayoutParams(lp);
-        }
-        int sideMargin = mResources.getDimensionPixelOffset(R.dimen.notification_side_paddings);
-        int topMargin = sideMargin;
-        lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
-        if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
-                || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
-            lp.width = qsWidth;
-            lp.gravity = panelGravity;
-            lp.leftMargin = sideMargin;
-            lp.rightMargin = sideMargin;
-            lp.topMargin = topMargin;
-            mPluginFrame.setLayoutParams(lp);
-        }
-    }
-
-    private void reInflateViews() {
-        updateShowEmptyShadeView();
-
-        // Re-inflate the status view group.
-        int index = mView.indexOfChild(mKeyguardStatusView);
-        mView.removeView(mKeyguardStatusView);
-        mKeyguardStatusView = (KeyguardStatusView) mInjectionInflationController.injectable(
-                LayoutInflater.from(mView.getContext())).inflate(
-                R.layout.keyguard_status_view, mView, false);
-        mView.addView(mKeyguardStatusView, index);
-
-        // Re-associate the clock container with the keyguard clock switch.
-        mBigClockContainer.removeAllViews();
-        KeyguardClockSwitch keyguardClockSwitch = mView.findViewById(R.id.keyguard_clock_container);
-        keyguardClockSwitch.setBigClockContainer(mBigClockContainer);
-
-        // Update keyguard bottom area
-        index = mView.indexOfChild(mKeyguardBottomArea);
-        mView.removeView(mKeyguardBottomArea);
-        KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
-        mKeyguardBottomArea = (KeyguardBottomAreaView) mInjectionInflationController.injectable(
-                LayoutInflater.from(mView.getContext())).inflate(
-                R.layout.keyguard_bottom_area, mView, false);
-        mKeyguardBottomArea.initFrom(oldBottomArea);
-        mView.addView(mKeyguardBottomArea, index);
-        initBottomArea();
-        mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
-        mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
-                mStatusBarStateController.getInterpolatedDozeAmount());
-
-        if (mKeyguardStatusBar != null) {
-            mKeyguardStatusBar.onThemeChanged();
-        }
-
-        setKeyguardStatusViewVisibility(mBarState, false, false);
-        setKeyguardBottomAreaVisibility(mBarState, false);
-        if (mOnReinflationListener != null) {
-            mOnReinflationListener.run();
-        }
-        reinflatePluginContainer();
-    }
-
-    private void reinflatePluginContainer() {
-        int index = mView.indexOfChild(mPluginFrame);
-        mView.removeView(mPluginFrame);
-        mPluginFrame = (FrameLayout) mInjectionInflationController.injectable(
-                LayoutInflater.from(mView.getContext())).inflate(
-                R.layout.status_bar_expanded_plugin_frame, mView, false);
-        mView.addView(mPluginFrame, index);
-
-        Resources res = mView.getResources();
-        int qsWidth = res.getDimensionPixelSize(R.dimen.qs_panel_width);
-        int panelGravity = mView.getResources().getInteger(
-                R.integer.notification_panel_layout_gravity);
-        FrameLayout.LayoutParams lp;
-        int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
-        int topMargin = res.getDimensionPixelOffset(
-                com.android.internal.R.dimen.quick_qs_total_height);
-        if (Utils.useQsMediaPlayer(mView.getContext())) {
-            topMargin = res.getDimensionPixelOffset(
-                    com.android.internal.R.dimen.quick_qs_total_height_with_media);
-        }
-        lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
-        if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
-                || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
-            lp.width = qsWidth;
-            lp.gravity = panelGravity;
-            lp.leftMargin = sideMargin;
-            lp.rightMargin = sideMargin;
-            lp.topMargin = topMargin;
-            mPluginFrame.setLayoutParams(lp);
-        }
-
-        if (mNPVPluginManager != null) mNPVPluginManager.replaceFrameLayout(mPluginFrame);
-    }
-
-    private void initBottomArea() {
-        mAffordanceHelper = new KeyguardAffordanceHelper(
-                mKeyguardAffordanceHelperCallback, mView.getContext(), mFalsingManager);
-        mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
-        mKeyguardBottomArea.setStatusBar(mStatusBar);
-        mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
-    }
-
-    public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
-        mKeyguardIndicationController = indicationController;
-        mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
-    }
-
-    private void updateGestureExclusionRect() {
-        Rect exclusionRect = calculateGestureExclusionRect();
-        mView.setSystemGestureExclusionRects(exclusionRect.isEmpty() ? Collections.EMPTY_LIST
-                : Collections.singletonList(exclusionRect));
-    }
-
-    private Rect calculateGestureExclusionRect() {
-        Rect exclusionRect = null;
-        Region touchableRegion = mHeadsUpManager.calculateTouchableRegion();
-        if (isFullyCollapsed() && touchableRegion != null) {
-            // Note: The heads up manager also calculates the non-pinned touchable region
-            exclusionRect = touchableRegion.getBounds();
-        }
-        return exclusionRect != null ? exclusionRect : EMPTY_RECT;
-    }
-
-    private void setIsFullWidth(boolean isFullWidth) {
-        mIsFullWidth = isFullWidth;
-        mNotificationStackScroller.setIsFullWidth(isFullWidth);
-    }
-
-    private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
-        if (mQsSizeChangeAnimator != null) {
-            oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
-            mQsSizeChangeAnimator.cancel();
-        }
-        mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
-        mQsSizeChangeAnimator.setDuration(300);
-        mQsSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                requestScrollerTopPaddingUpdate(false /* animate */);
-                requestPanelHeightUpdate();
-                int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
-                mQs.setHeightOverride(height);
-            }
-        });
-        mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mQsSizeChangeAnimator = null;
-            }
-        });
-        mQsSizeChangeAnimator.start();
-    }
-
-    /**
-     * Positions the clock and notifications dynamically depending on how many notifications are
-     * showing.
-     */
-    private void positionClockAndNotifications() {
-        boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
-        boolean animateClock = animate || mAnimateNextPositionUpdate;
-        int stackScrollerPadding;
-        if (mBarState != StatusBarState.KEYGUARD) {
-            stackScrollerPadding = getUnlockedStackScrollerPadding();
-        } else {
-            int totalHeight = mView.getHeight();
-            int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
-            int clockPreferredY = mKeyguardStatusView.getClockPreferredY(totalHeight);
-            boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
-            final boolean
-                    hasVisibleNotifications =
-                    !bypassEnabled && mNotificationStackScroller.getVisibleNotificationCount() != 0;
-            mKeyguardStatusView.setHasVisibleNotifications(hasVisibleNotifications);
-            mClockPositionAlgorithm.setup(mStatusBarMinHeight, totalHeight - bottomPadding,
-                    mNotificationStackScroller.getIntrinsicContentHeight(), getExpandedFraction(),
-                    totalHeight, (int) (mKeyguardStatusView.getHeight() - mShelfHeight / 2.0f
-                            - mDarkIconSize / 2.0f), clockPreferredY, hasCustomClock(),
-                    hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
-                    bypassEnabled, getUnlockedStackScrollerPadding());
-            mClockPositionAlgorithm.run(mClockPositionResult);
-            PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.X,
-                    mClockPositionResult.clockX, CLOCK_ANIMATION_PROPERTIES, animateClock);
-            PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.Y,
-                    mClockPositionResult.clockY, CLOCK_ANIMATION_PROPERTIES, animateClock);
-            updateNotificationTranslucency();
-            updateClock();
-            stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
-        }
-        mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
-        mKeyguardBottomArea.setAntiBurnInOffsetX(mClockPositionResult.clockX);
-
-        mStackScrollerMeasuringPass++;
-        requestScrollerTopPaddingUpdate(animate);
-        mStackScrollerMeasuringPass = 0;
-        mAnimateNextPositionUpdate = false;
-    }
-
-    /**
-     * @return the padding of the stackscroller when unlocked
-     */
-    private int getUnlockedStackScrollerPadding() {
-        return (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight
-                + mQsNotificationTopPadding;
-    }
-
-    /**
-     * @param maximum the maximum to return at most
-     * @return the maximum keyguard notifications that can fit on the screen
-     */
-    public int computeMaxKeyguardNotifications(int maximum) {
-        float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding();
-        int notificationPadding = Math.max(
-                1, mResources.getDimensionPixelSize(R.dimen.notification_divider_height));
-        NotificationShelf shelf = mNotificationStackScroller.getNotificationShelf();
-        float
-                shelfSize =
-                shelf.getVisibility() == View.GONE ? 0
-                        : shelf.getIntrinsicHeight() + notificationPadding;
-        float
-                availableSpace =
-                mNotificationStackScroller.getHeight() - minPadding - shelfSize - Math.max(
-                        mIndicationBottomPadding, mAmbientIndicationBottomPadding)
-                        - mKeyguardStatusView.getLogoutButtonHeight();
-        int count = 0;
-        for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
-            ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
-                continue;
-            }
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            boolean
-                    suppressedSummary =
-                    mGroupManager != null && mGroupManager.isSummaryOfSuppressedGroup(
-                            row.getEntry().getSbn());
-            if (suppressedSummary) {
-                continue;
-            }
-            if (!mLockscreenUserManager.shouldShowOnKeyguard(row.getEntry())) {
-                continue;
-            }
-            if (row.isRemoved()) {
-                continue;
-            }
-            availableSpace -= child.getMinHeight(true /* ignoreTemporaryStates */)
-                    + notificationPadding;
-            if (availableSpace >= 0 && count < maximum) {
-                count++;
-            } else if (availableSpace > -shelfSize) {
-                // if we are exactly the last view, then we can show us still!
-                for (int j = i + 1; j < mNotificationStackScroller.getChildCount(); j++) {
-                    if (mNotificationStackScroller.getChildAt(
-                            j) instanceof ExpandableNotificationRow) {
-                        return count;
-                    }
-                }
-                count++;
-                return count;
-            } else {
-                return count;
-            }
-        }
-        return count;
-    }
-
-    private void updateClock() {
-        if (!mKeyguardStatusViewAnimating) {
-            mKeyguardStatusView.setAlpha(mClockPositionResult.clockAlpha);
-        }
-    }
-
-    public void animateToFullShade(long delay) {
-        mNotificationStackScroller.goToFullShade(delay);
-        mView.requestLayout();
-        mAnimateNextPositionUpdate = true;
-    }
-
-    public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
-        mQsExpansionEnabled = qsExpansionEnabled;
-        if (mQs == null) return;
-        mQs.setHeaderClickable(qsExpansionEnabled);
-    }
-
-    @Override
-    public void resetViews(boolean animate) {
-        mIsLaunchTransitionFinished = false;
-        mBlockTouches = false;
-        if (!mLaunchingAffordance) {
-            mAffordanceHelper.reset(false);
-            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
-        }
-        mStatusBar.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
-                true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
-        if (animate) {
-            animateCloseQs(true /* animateAway */);
-        } else {
-            closeQs();
-        }
-        mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, animate,
-                !animate /* cancelAnimators */);
-        mNotificationStackScroller.resetScrollPosition();
-    }
-
-    @Override
-    public void collapse(boolean delayed, float speedUpFactor) {
-        if (!canPanelBeCollapsed()) {
-            return;
-        }
-
-        if (mQsExpanded) {
-            mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
-        }
-        super.collapse(delayed, speedUpFactor);
-    }
-
-    public void closeQs() {
-        cancelQsAnimation();
-        setQsExpansion(mQsMinExpansionHeight);
-    }
-
-    public void cancelAnimation() {
-        mView.animate().cancel();
-    }
-
-
-    /**
-     * Animate QS closing by flinging it.
-     * If QS is expanded, it will collapse into QQS and stop.
-     *
-     * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
-     */
-    public void animateCloseQs(boolean animateAway) {
-        if (mQsExpansionAnimator != null) {
-            if (!mQsAnimatorExpand) {
-                return;
-            }
-            float height = mQsExpansionHeight;
-            mQsExpansionAnimator.cancel();
-            setQsExpansion(height);
-        }
-        flingSettings(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE);
-    }
-
-    public void expandWithQs() {
-        if (mQsExpansionEnabled) {
-            mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
-        }
-        if (isFullyCollapsed()) {
-            expand(true /* animate */);
-        } else {
-            flingSettings(0 /* velocity */, FLING_EXPAND);
-        }
-    }
-
-    public void expandWithoutQs() {
-        if (isQsExpanded()) {
-            flingSettings(0 /* velocity */, FLING_COLLAPSE);
-        } else {
-            expand(true /* animate */);
-        }
-    }
-
-    @Override
-    public void fling(float vel, boolean expand) {
-        GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
-        if (gr != null) {
-            gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
-        }
-        super.fling(vel, expand);
-    }
-
-    @Override
-    protected void flingToHeight(float vel, boolean expand, float target,
-            float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
-        mHeadsUpTouchHelper.notifyFling(!expand);
-        setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f);
-        super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
-    }
-
-
-    private boolean onQsIntercept(MotionEvent event) {
-        int pointerIndex = event.findPointerIndex(mTrackingPointer);
-        if (pointerIndex < 0) {
-            pointerIndex = 0;
-            mTrackingPointer = event.getPointerId(pointerIndex);
-        }
-        final float x = event.getX(pointerIndex);
-        final float y = event.getY(pointerIndex);
-
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                mInitialTouchY = y;
-                mInitialTouchX = x;
-                initVelocityTracker();
-                trackMovement(event);
-                if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
-                    mView.getParent().requestDisallowInterceptTouchEvent(true);
-                }
-                if (mQsExpansionAnimator != null) {
-                    onQsExpansionStarted();
-                    mInitialHeightOnTouch = mQsExpansionHeight;
-                    mQsTracking = true;
-                    mNotificationStackScroller.cancelLongPress();
-                }
-                break;
-            case MotionEvent.ACTION_POINTER_UP:
-                final int upPointer = event.getPointerId(event.getActionIndex());
-                if (mTrackingPointer == upPointer) {
-                    // gesture is ongoing, find a new pointer to track
-                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
-                    mTrackingPointer = event.getPointerId(newIndex);
-                    mInitialTouchX = event.getX(newIndex);
-                    mInitialTouchY = event.getY(newIndex);
-                }
-                break;
-
-            case MotionEvent.ACTION_MOVE:
-                final float h = y - mInitialTouchY;
-                trackMovement(event);
-                if (mQsTracking) {
-
-                    // Already tracking because onOverscrolled was called. We need to update here
-                    // so we don't stop for a frame until the next touch event gets handled in
-                    // onTouchEvent.
-                    setQsExpansion(h + mInitialHeightOnTouch);
-                    trackMovement(event);
-                    return true;
-                }
-                if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
-                        && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
-                    mQsTracking = true;
-                    onQsExpansionStarted();
-                    notifyExpandingFinished();
-                    mInitialHeightOnTouch = mQsExpansionHeight;
-                    mInitialTouchY = y;
-                    mInitialTouchX = x;
-                    mNotificationStackScroller.cancelLongPress();
-                    return true;
-                }
-                break;
-
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                trackMovement(event);
-                if (mQsTracking) {
-                    flingQsWithCurrentVelocity(y,
-                            event.getActionMasked() == MotionEvent.ACTION_CANCEL);
-                    mQsTracking = false;
-                }
-                break;
-        }
-        return false;
-    }
-
-    @Override
-    protected boolean isInContentBounds(float x, float y) {
-        float stackScrollerX = mNotificationStackScroller.getX();
-        return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y)
-                && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth();
-    }
-
-    private void initDownStates(MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            mOnlyAffordanceInThisMotion = false;
-            mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
-            mDozingOnDown = isDozing();
-            mDownX = event.getX();
-            mDownY = event.getY();
-            mCollapsedOnDown = isFullyCollapsed();
-            mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
-            mAllowExpandForSmallExpansion = mExpectingSynthesizedDown;
-            mTouchSlopExceededBeforeDown = mExpectingSynthesizedDown;
-            if (mExpectingSynthesizedDown) {
-                mLastEventSynthesizedDown = true;
-            } else {
-                // down but not synthesized motion event.
-                mLastEventSynthesizedDown = false;
-            }
-        } else {
-            // not down event at all.
-            mLastEventSynthesizedDown = false;
-        }
-    }
-
-    private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
-        float vel = getCurrentQSVelocity();
-        final boolean expandsQs = flingExpandsQs(vel);
-        if (expandsQs) {
-            logQsSwipeDown(y);
-        }
-        flingSettings(vel, expandsQs && !isCancelMotionEvent ? FLING_EXPAND : FLING_COLLAPSE);
-    }
-
-    private void logQsSwipeDown(float y) {
-        float vel = getCurrentQSVelocity();
-        final int
-                gesture =
-                mBarState == StatusBarState.KEYGUARD ? MetricsEvent.ACTION_LS_QS
-                        : MetricsEvent.ACTION_SHADE_QS_PULL;
-        mLockscreenGestureLogger.write(gesture,
-                (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()),
-                (int) (vel / mStatusBar.getDisplayDensity()));
-    }
-
-    private boolean flingExpandsQs(float vel) {
-        if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) {
-            return false;
-        }
-        if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
-            return getQsExpansionFraction() > 0.5f;
-        } else {
-            return vel > 0;
-        }
-    }
-
-    private boolean isFalseTouch() {
-        if (!mKeyguardAffordanceHelperCallback.needsAntiFalsing()) {
-            return false;
-        }
-        if (mFalsingManager.isClassifierEnabled()) {
-            return mFalsingManager.isFalseTouch();
-        }
-        return !mQsTouchAboveFalsingThreshold;
-    }
-
-    private float getQsExpansionFraction() {
-        return Math.min(
-                1f, (mQsExpansionHeight - mQsMinExpansionHeight) / (mQsMaxExpansionHeight
-                        - mQsMinExpansionHeight));
-    }
-
-    @Override
-    protected boolean shouldExpandWhenNotFlinging() {
-        if (super.shouldExpandWhenNotFlinging()) {
-            return true;
-        }
-        if (mAllowExpandForSmallExpansion) {
-            // When we get a touch that came over from launcher, the velocity isn't always correct
-            // Let's err on expanding if the gesture has been reasonably slow
-            long timeSinceDown = SystemClock.uptimeMillis() - mDownTime;
-            return timeSinceDown <= MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER;
-        }
-        return false;
-    }
-
-    @Override
-    protected float getOpeningHeight() {
-        return mNotificationStackScroller.getOpeningHeight();
-    }
-
-
-    private boolean handleQsTouch(MotionEvent event) {
-        final int action = event.getActionMasked();
-        if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
-                && mBarState != StatusBarState.KEYGUARD && !mQsExpanded && mQsExpansionEnabled) {
-
-            // Down in the empty area while fully expanded - go to QS.
-            mQsTracking = true;
-            mConflictingQsExpansionGesture = true;
-            onQsExpansionStarted();
-            mInitialHeightOnTouch = mQsExpansionHeight;
-            mInitialTouchY = event.getX();
-            mInitialTouchX = event.getY();
-        }
-        if (!isFullyCollapsed()) {
-            handleQsDown(event);
-        }
-        if (!mQsExpandImmediate && mQsTracking) {
-            onQsTouch(event);
-            if (!mConflictingQsExpansionGesture) {
-                return true;
-            }
-        }
-        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
-            mConflictingQsExpansionGesture = false;
-        }
-        if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed() && mQsExpansionEnabled) {
-            mTwoFingerQsExpandPossible = true;
-        }
-        if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) && event.getY(event.getActionIndex())
-                < mStatusBarMinHeight) {
-            mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1);
-            mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
-            requestPanelHeightUpdate();
-
-            // Normally, we start listening when the panel is expanded, but here we need to start
-            // earlier so the state is already up to date when dragging down.
-            setListening(true);
-        }
-        if (isQsSplitEnabled() && !mKeyguardShowing) {
-            if (mQsExpandImmediate) {
-                mNotificationStackScroller.setVisibility(View.GONE);
-                mQsFrame.setVisibility(View.VISIBLE);
-                mHomeControlsLayout.setVisibility(View.VISIBLE);
-            } else {
-                mNotificationStackScroller.setVisibility(View.VISIBLE);
-                mQsFrame.setVisibility(View.GONE);
-                mHomeControlsLayout.setVisibility(View.GONE);
-            }
-        }
-        return false;
-    }
-
-    private boolean isInQsArea(float x, float y) {
-        return (x >= mQsFrame.getX() && x <= mQsFrame.getX() + mQsFrame.getWidth()) && (
-                y <= mNotificationStackScroller.getBottomMostNotificationBottom()
-                        || y <= mQs.getView().getY() + mQs.getView().getHeight());
-    }
-
-    private boolean isOnQsEndArea(float x) {
-        if (!isQsSplitEnabled()) return false;
-        if (mView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR) {
-            return x >= mQsFrame.getX() + mQqsSplitFraction * mQsFrame.getWidth()
-                    && x <= mQsFrame.getX() + mQsFrame.getWidth();
-        } else {
-            return x >= mQsFrame.getX()
-                    && x <= mQsFrame.getX() + (1 - mQqsSplitFraction) * mQsFrame.getWidth();
-        }
-    }
-
-    private boolean isOpenQsEvent(MotionEvent event) {
-        final int pointerCount = event.getPointerCount();
-        final int action = event.getActionMasked();
-
-        final boolean
-                twoFingerDrag =
-                action == MotionEvent.ACTION_POINTER_DOWN && pointerCount == 2;
-
-        final boolean
-                stylusButtonClickDrag =
-                action == MotionEvent.ACTION_DOWN && (event.isButtonPressed(
-                        MotionEvent.BUTTON_STYLUS_PRIMARY) || event.isButtonPressed(
-                        MotionEvent.BUTTON_STYLUS_SECONDARY));
-
-        final boolean
-                mouseButtonClickDrag =
-                action == MotionEvent.ACTION_DOWN && (event.isButtonPressed(
-                        MotionEvent.BUTTON_SECONDARY) || event.isButtonPressed(
-                        MotionEvent.BUTTON_TERTIARY));
-
-        final boolean onHeaderRight = isOnQsEndArea(event.getX());
-
-        return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag || onHeaderRight;
-    }
-
-    private void handleQsDown(MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN && shouldQuickSettingsIntercept(
-                event.getX(), event.getY(), -1)) {
-            mFalsingManager.onQsDown();
-            mQsTracking = true;
-            onQsExpansionStarted();
-            mInitialHeightOnTouch = mQsExpansionHeight;
-            mInitialTouchY = event.getX();
-            mInitialTouchX = event.getY();
-
-            // If we interrupt an expansion gesture here, make sure to update the state correctly.
-            notifyExpandingFinished();
-        }
-    }
-
-    /**
-     * Input focus transfer is about to happen.
-     */
-    public void startWaitingForOpenPanelGesture() {
-        if (!isFullyCollapsed()) {
-            return;
-        }
-        mExpectingSynthesizedDown = true;
-        onTrackingStarted();
-        updatePanelExpanded();
-    }
-
-    /**
-     * Called when this view is no longer waiting for input focus transfer.
-     *
-     * There are two scenarios behind this function call. First, input focus transfer
-     * has successfully happened and this view already received synthetic DOWN event.
-     * (mExpectingSynthesizedDown == false). Do nothing.
-     *
-     * Second, before input focus transfer finished, user may have lifted finger
-     * in previous window and this window never received synthetic DOWN event.
-     * (mExpectingSynthesizedDown == true).
-     * In this case, we use the velocity to trigger fling event.
-     *
-     * @param velocity unit is in px / millis
-     */
-    public void stopWaitingForOpenPanelGesture(final float velocity) {
-        if (mExpectingSynthesizedDown) {
-            mExpectingSynthesizedDown = false;
-            maybeVibrateOnOpening();
-            Runnable runnable = () -> fling(velocity > 1f ? 1000f * velocity : 0,
-                    true /* expand */);
-            if (mStatusBar.getStatusBarWindow().getHeight() != mStatusBar.getStatusBarHeight()) {
-                // The panel is already expanded to its full size, let's expand directly
-                runnable.run();
-            } else {
-                mExpandAfterLayoutRunnable = runnable;
-            }
-            onTrackingStopped(false);
-        }
-    }
-
-    @Override
-    protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
-        boolean expands = super.flingExpands(vel, vectorVel, x, y);
-
-        // If we are already running a QS expansion, make sure that we keep the panel open.
-        if (mQsExpansionAnimator != null) {
-            expands = true;
-        }
-        return expands;
-    }
-
-    @Override
-    protected boolean shouldGestureWaitForTouchSlop() {
-        if (mExpectingSynthesizedDown) {
-            mExpectingSynthesizedDown = false;
-            return false;
-        }
-        return isFullyCollapsed() || mBarState != StatusBarState.SHADE;
-    }
-
-    @Override
-    protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
-        return !mAffordanceHelper.isOnAffordanceIcon(x, y);
-    }
-
-    private void onQsTouch(MotionEvent event) {
-        int pointerIndex = event.findPointerIndex(mTrackingPointer);
-        if (pointerIndex < 0) {
-            pointerIndex = 0;
-            mTrackingPointer = event.getPointerId(pointerIndex);
-        }
-        final float y = event.getY(pointerIndex);
-        final float x = event.getX(pointerIndex);
-        final float h = y - mInitialTouchY;
-
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN:
-                mQsTracking = true;
-                mInitialTouchY = y;
-                mInitialTouchX = x;
-                onQsExpansionStarted();
-                mInitialHeightOnTouch = mQsExpansionHeight;
-                initVelocityTracker();
-                trackMovement(event);
-                break;
-
-            case MotionEvent.ACTION_POINTER_UP:
-                final int upPointer = event.getPointerId(event.getActionIndex());
-                if (mTrackingPointer == upPointer) {
-                    // gesture is ongoing, find a new pointer to track
-                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
-                    final float newY = event.getY(newIndex);
-                    final float newX = event.getX(newIndex);
-                    mTrackingPointer = event.getPointerId(newIndex);
-                    mInitialHeightOnTouch = mQsExpansionHeight;
-                    mInitialTouchY = newY;
-                    mInitialTouchX = newX;
-                }
-                break;
-
-            case MotionEvent.ACTION_MOVE:
-                setQsExpansion(h + mInitialHeightOnTouch);
-                if (h >= getFalsingThreshold()) {
-                    mQsTouchAboveFalsingThreshold = true;
-                }
-                trackMovement(event);
-                break;
-
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                mQsTracking = false;
-                mTrackingPointer = -1;
-                trackMovement(event);
-                float fraction = getQsExpansionFraction();
-                if (fraction != 0f || y >= mInitialTouchY) {
-                    flingQsWithCurrentVelocity(y,
-                            event.getActionMasked() == MotionEvent.ACTION_CANCEL);
-                }
-                if (mQsVelocityTracker != null) {
-                    mQsVelocityTracker.recycle();
-                    mQsVelocityTracker = null;
-                }
-                break;
-        }
-    }
-
-    private int getFalsingThreshold() {
-        float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
-        return (int) (mQsFalsingThreshold * factor);
-    }
-
-    private void setOverScrolling(boolean overscrolling) {
-        mStackScrollerOverscrolling = overscrolling;
-        if (mQs == null) return;
-        mQs.setOverscrolling(overscrolling);
-    }
-
-    private void onQsExpansionStarted() {
-        onQsExpansionStarted(0);
-    }
-
-    protected void onQsExpansionStarted(int overscrollAmount) {
-        cancelQsAnimation();
-        cancelHeightAnimator();
-
-        // Reset scroll position and apply that position to the expanded height.
-        float height = mQsExpansionHeight - overscrollAmount;
-        setQsExpansion(height);
-        requestPanelHeightUpdate();
-        mNotificationStackScroller.checkSnoozeLeavebehind();
-
-        // When expanding QS, let's authenticate the user if possible,
-        // this will speed up notification actions.
-        if (height == 0) {
-            mStatusBar.requestFaceAuth();
-        }
-    }
-
-    private void setQsExpanded(boolean expanded) {
-        boolean changed = mQsExpanded != expanded;
-        if (changed) {
-            mQsExpanded = expanded;
-            updateQsState();
-            requestPanelHeightUpdate();
-            mFalsingManager.setQsExpanded(expanded);
-            mStatusBar.setQsExpanded(expanded);
-            mNotificationContainerParent.setQsExpanded(expanded);
-            mPulseExpansionHandler.setQsExpanded(expanded);
-            mKeyguardBypassController.setQSExpanded(expanded);
-        }
-    }
-
-    private void maybeAnimateBottomAreaAlpha() {
-        mBottomAreaShadeAlphaAnimator.cancel();
-        if (mBarState == StatusBarState.SHADE_LOCKED) {
-            mBottomAreaShadeAlphaAnimator.start();
-        } else {
-            mBottomAreaShadeAlpha = 1f;
-        }
-    }
-
-    private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mKeyguardStatusViewAnimating = false;
-            mKeyguardStatusView.setVisibility(View.INVISIBLE);
-        }
-    };
-
-    private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mKeyguardStatusViewAnimating = false;
-            mKeyguardStatusView.setVisibility(View.GONE);
-        }
-    };
-
-    private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mKeyguardStatusViewAnimating = false;
-        }
-    };
-
-    private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mKeyguardStatusBar.setVisibility(View.INVISIBLE);
-            mKeyguardStatusBar.setAlpha(1f);
-            mKeyguardStatusBarAnimateAlpha = 1f;
-        }
-    };
-
-    private void animateKeyguardStatusBarOut() {
-        ValueAnimator anim = ValueAnimator.ofFloat(mKeyguardStatusBar.getAlpha(), 0f);
-        anim.addUpdateListener(mStatusBarAnimateAlphaListener);
-        anim.setStartDelay(mKeyguardStateController.isKeyguardFadingAway()
-                ? mKeyguardStateController.getKeyguardFadingAwayDelay() : 0);
-
-        long duration;
-        if (mKeyguardStateController.isKeyguardFadingAway()) {
-            duration = mKeyguardStateController.getShortenedFadingAwayDuration();
-        } else {
-            duration = StackStateAnimator.ANIMATION_DURATION_STANDARD;
-        }
-        anim.setDuration(duration);
-
-        anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mAnimateKeyguardStatusBarInvisibleEndRunnable.run();
-            }
-        });
-        anim.start();
-    }
-
-    private final ValueAnimator.AnimatorUpdateListener
-            mStatusBarAnimateAlphaListener =
-            new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
-                    updateHeaderKeyguardAlpha();
-                }
-            };
-
-    private void animateKeyguardStatusBarIn(long duration) {
-        mKeyguardStatusBar.setVisibility(View.VISIBLE);
-        mKeyguardStatusBar.setAlpha(0f);
-        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
-        anim.addUpdateListener(mStatusBarAnimateAlphaListener);
-        anim.setDuration(duration);
-        anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-        anim.start();
-    }
-
-    private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mKeyguardBottomArea.setVisibility(View.GONE);
-        }
-    };
-
-    private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
-        mKeyguardBottomArea.animate().cancel();
-        if (goingToFullShade) {
-            mKeyguardBottomArea.animate().alpha(0f).setStartDelay(
-                    mKeyguardStateController.getKeyguardFadingAwayDelay()).setDuration(
-                    mKeyguardStateController.getShortenedFadingAwayDuration()).setInterpolator(
-                    Interpolators.ALPHA_OUT).withEndAction(
-                    mAnimateKeyguardBottomAreaInvisibleEndRunnable).start();
-        } else if (statusBarState == StatusBarState.KEYGUARD
-                || statusBarState == StatusBarState.SHADE_LOCKED) {
-            mKeyguardBottomArea.setVisibility(View.VISIBLE);
-            mKeyguardBottomArea.setAlpha(1f);
-        } else {
-            mKeyguardBottomArea.setVisibility(View.GONE);
-        }
-    }
-
-    private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway,
-            boolean goingToFullShade) {
-        mKeyguardStatusView.animate().cancel();
-        mKeyguardStatusViewAnimating = false;
-        if ((!keyguardFadingAway && mBarState == StatusBarState.KEYGUARD
-                && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) {
-            mKeyguardStatusViewAnimating = true;
-            mKeyguardStatusView.animate().alpha(0f).setStartDelay(0).setDuration(
-                    160).setInterpolator(Interpolators.ALPHA_OUT).withEndAction(
-                    mAnimateKeyguardStatusViewGoneEndRunnable);
-            if (keyguardFadingAway) {
-                mKeyguardStatusView.animate().setStartDelay(
-                        mKeyguardStateController.getKeyguardFadingAwayDelay()).setDuration(
-                        mKeyguardStateController.getShortenedFadingAwayDuration()).start();
-            }
-        } else if (mBarState == StatusBarState.SHADE_LOCKED
-                && statusBarState == StatusBarState.KEYGUARD) {
-            mKeyguardStatusView.setVisibility(View.VISIBLE);
-            mKeyguardStatusViewAnimating = true;
-            mKeyguardStatusView.setAlpha(0f);
-            mKeyguardStatusView.animate().alpha(1f).setStartDelay(0).setDuration(
-                    320).setInterpolator(Interpolators.ALPHA_IN).withEndAction(
-                    mAnimateKeyguardStatusViewVisibleEndRunnable);
-        } else if (statusBarState == StatusBarState.KEYGUARD) {
-            if (keyguardFadingAway) {
-                mKeyguardStatusViewAnimating = true;
-                mKeyguardStatusView.animate().alpha(0).translationYBy(
-                        -getHeight() * 0.05f).setInterpolator(
-                        Interpolators.FAST_OUT_LINEAR_IN).setDuration(125).setStartDelay(
-                        0).withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable).start();
-            } else {
-                mKeyguardStatusView.setVisibility(View.VISIBLE);
-                mKeyguardStatusView.setAlpha(1f);
-            }
-        } else {
-            mKeyguardStatusView.setVisibility(View.GONE);
-            mKeyguardStatusView.setAlpha(1f);
-        }
-    }
-
-    private void updateQsState() {
-        mNotificationStackScroller.setQsExpanded(mQsExpanded);
-        mNotificationStackScroller.setScrollingEnabled(
-                mBarState != StatusBarState.KEYGUARD && (!mQsExpanded
-                        || mQsExpansionFromOverscroll));
-        updateEmptyShadeView();
-        if (mNPVPluginManager != null) {
-            mNPVPluginManager.changeVisibility(
-                    (mBarState != StatusBarState.KEYGUARD) ? View.VISIBLE : View.INVISIBLE);
-        }
-        mQsNavbarScrim.setVisibility(
-                mBarState == StatusBarState.SHADE && mQsExpanded && !mStackScrollerOverscrolling
-                        && mQsScrimEnabled ? View.VISIBLE : View.INVISIBLE);
-        if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
-            mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
-        }
-        if (mQs == null) return;
-        mQs.setExpanded(mQsExpanded);
-    }
-
-    private void setQsExpansion(float height) {
-        height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
-        mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
-        if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling
-                && !mDozing) {
-            setQsExpanded(true);
-        } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
-            setQsExpanded(false);
-        }
-        mQsExpansionHeight = height;
-        updateQsExpansion();
-        requestScrollerTopPaddingUpdate(false /* animate */);
-        updateHeaderKeyguardAlpha();
-        if (mBarState == StatusBarState.SHADE_LOCKED || mBarState == StatusBarState.KEYGUARD) {
-            updateKeyguardBottomAreaAlpha();
-            updateBigClockAlpha();
-        }
-        if (mBarState == StatusBarState.SHADE && mQsExpanded && !mStackScrollerOverscrolling
-                && mQsScrimEnabled) {
-            mQsNavbarScrim.setAlpha(getQsExpansionFraction());
-        }
-
-        if (mAccessibilityManager.isEnabled()) {
-            mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
-        }
-
-        if (!mFalsingManager.isUnlockingDisabled() && mQsFullyExpanded
-                && mFalsingManager.shouldEnforceBouncer()) {
-            mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
-                    false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
-        }
-        for (int i = 0; i < mExpansionListeners.size(); i++) {
-            mExpansionListeners.get(i).onQsExpansionChanged(
-                    mQsMaxExpansionHeight != 0 ? mQsExpansionHeight / mQsMaxExpansionHeight : 0);
-        }
-        if (DEBUG) {
-            mView.invalidate();
-        }
-    }
-
-    protected void updateQsExpansion() {
-        if (mQs == null) return;
-        float qsExpansionFraction = getQsExpansionFraction();
-        mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
-        int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight();
-        if (mNPVPluginManager != null) {
-            mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff);
-        }
-        mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
-    }
-
-    private String determineAccessibilityPaneTitle() {
-        if (mQs != null && mQs.isCustomizing()) {
-            return mResources.getString(R.string.accessibility_desc_quick_settings_edit);
-        } else if (mQsExpansionHeight != 0.0f && mQsFullyExpanded) {
-            // Upon initialisation when we are not layouted yet we don't want to announce that we
-            // are fully expanded, hence the != 0.0f check.
-            return mResources.getString(R.string.accessibility_desc_quick_settings);
-        } else if (mBarState == StatusBarState.KEYGUARD) {
-            return mResources.getString(R.string.accessibility_desc_lock_screen);
-        } else {
-            return mResources.getString(R.string.accessibility_desc_notification_shade);
-        }
-    }
-
-    private float calculateQsTopPadding() {
-        if (mKeyguardShowing && (mQsExpandImmediate
-                || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
-
-            // Either QS pushes the notifications down when fully expanded, or QS is fully above the
-            // notifications (mostly on tablets). maxNotificationPadding denotes the normal top
-            // padding on Keyguard, maxQsPadding denotes the top padding from the quick settings
-            // panel. We need to take the maximum and linearly interpolate with the panel expansion
-            // for a nice motion.
-            int maxNotificationPadding = getKeyguardNotificationStaticPadding();
-            int maxQsPadding = mQsMaxExpansionHeight + mQsNotificationTopPadding;
-            int max = mBarState == StatusBarState.KEYGUARD ? Math.max(
-                    maxNotificationPadding, maxQsPadding) : maxQsPadding;
-            return (int) MathUtils.lerp((float) mQsMinExpansionHeight, (float) max,
-                    getExpandedFraction());
-        } else if (mQsSizeChangeAnimator != null) {
-            return Math.max(
-                    (int) mQsSizeChangeAnimator.getAnimatedValue(),
-                    getKeyguardNotificationStaticPadding());
-        } else if (mKeyguardShowing) {
-            // We can only do the smoother transition on Keyguard when we also are not collapsing
-            // from a scrolled quick settings.
-            return MathUtils.lerp((float) getKeyguardNotificationStaticPadding(),
-                    (float) (mQsMaxExpansionHeight + mQsNotificationTopPadding),
-                    getQsExpansionFraction());
-        } else {
-            return mQsExpansionHeight + mQsNotificationTopPadding;
-        }
-    }
-
-    /**
-     * @return the topPadding of notifications when on keyguard not respecting quick settings
-     * expansion
-     */
-    private int getKeyguardNotificationStaticPadding() {
-        if (!mKeyguardShowing) {
-            return 0;
-        }
-        if (!mKeyguardBypassController.getBypassEnabled()) {
-            return mClockPositionResult.stackScrollerPadding;
-        }
-        int collapsedPosition = mHeadsUpInset;
-        if (!mNotificationStackScroller.isPulseExpanding()) {
-            return collapsedPosition;
-        } else {
-            int expandedPosition = mClockPositionResult.stackScrollerPadding;
-            return (int) MathUtils.lerp(collapsedPosition, expandedPosition,
-                    mNotificationStackScroller.calculateAppearFractionBypass());
-        }
-    }
-
-
-    protected void requestScrollerTopPaddingUpdate(boolean animate) {
-        mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), animate);
-        if (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()) {
-            // update the position of the header
-            updateQsExpansion();
-        }
-    }
-
-
-    private void updateQSPulseExpansion() {
-        if (mQs != null) {
-            mQs.setShowCollapsedOnKeyguard(
-                    mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()
-                            && mNotificationStackScroller.isPulseExpanding());
-        }
-    }
-
-    private void trackMovement(MotionEvent event) {
-        if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event);
-    }
-
-    private void initVelocityTracker() {
-        if (mQsVelocityTracker != null) {
-            mQsVelocityTracker.recycle();
-        }
-        mQsVelocityTracker = VelocityTracker.obtain();
-    }
-
-    private float getCurrentQSVelocity() {
-        if (mQsVelocityTracker == null) {
-            return 0;
-        }
-        mQsVelocityTracker.computeCurrentVelocity(1000);
-        return mQsVelocityTracker.getYVelocity();
-    }
-
-    private void cancelQsAnimation() {
-        if (mQsExpansionAnimator != null) {
-            mQsExpansionAnimator.cancel();
-        }
-    }
-
-    /**
-     * @see #flingSettings(float, int, Runnable, boolean)
-     */
-    public void flingSettings(float vel, int type) {
-        flingSettings(vel, type, null, false /* isClick */);
-    }
-
-    /**
-     * Animates QS or QQS as if the user had swiped up or down.
-     *
-     * @param vel              Finger velocity or 0 when not initiated by touch events.
-     * @param type             Either {@link #FLING_EXPAND}, {@link #FLING_COLLAPSE} or {@link
-     *                         #FLING_HIDE}.
-     * @param onFinishRunnable Runnable to be executed at the end of animation.
-     * @param isClick          If originated by click (different interpolator and duration.)
-     */
-    protected void flingSettings(float vel, int type, final Runnable onFinishRunnable,
-            boolean isClick) {
-        float target;
-        switch (type) {
-            case FLING_EXPAND:
-                target = mQsMaxExpansionHeight;
-                break;
-            case FLING_COLLAPSE:
-                target = mQsMinExpansionHeight;
-                break;
-            case FLING_HIDE:
-            default:
-                target = 0;
-        }
-        if (target == mQsExpansionHeight) {
-            if (onFinishRunnable != null) {
-                onFinishRunnable.run();
-            }
-            return;
-        }
-
-        // If we move in the opposite direction, reset velocity and use a different duration.
-        boolean oppositeDirection = false;
-        boolean expanding = type == FLING_EXPAND;
-        if (vel > 0 && !expanding || vel < 0 && expanding) {
-            vel = 0;
-            oppositeDirection = true;
-        }
-        ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
-        if (isClick) {
-            animator.setInterpolator(Interpolators.TOUCH_RESPONSE);
-            animator.setDuration(368);
-        } else {
-            mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
-        }
-        if (oppositeDirection) {
-            animator.setDuration(350);
-        }
-        animator.addUpdateListener(animation -> {
-            setQsExpansion((Float) animation.getAnimatedValue());
-        });
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mNotificationStackScroller.resetCheckSnoozeLeavebehind();
-                mQsExpansionAnimator = null;
-                if (onFinishRunnable != null) {
-                    onFinishRunnable.run();
-                }
-            }
-        });
-        animator.start();
-        mQsExpansionAnimator = animator;
-        mQsAnimatorExpand = expanding;
-    }
-
-    /**
-     * @return Whether we should intercept a gesture to open Quick Settings.
-     */
-    private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
-        if (!mQsExpansionEnabled || mCollapsedOnDown || (mKeyguardShowing
-                && mKeyguardBypassController.getBypassEnabled())) {
-            return false;
-        }
-        View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
-        final boolean
-                onHeader =
-                x >= mQsFrame.getX() && x <= mQsFrame.getX() + mQsFrame.getWidth()
-                        && y >= header.getTop() && y <= header.getBottom();
-        if (mQsExpanded) {
-            return onHeader || (yDiff < 0 && isInQsArea(x, y));
-        } else {
-            return onHeader;
-        }
-    }
-
-    @Override
-    protected boolean isScrolledToBottom() {
-        if (!isInSettings()) {
-            return mBarState == StatusBarState.KEYGUARD
-                    || mNotificationStackScroller.isScrolledToBottom();
-        } else {
-            return true;
-        }
-    }
-
-    @Override
-    protected int getMaxPanelHeight() {
-        if (mKeyguardBypassController.getBypassEnabled() && mBarState == StatusBarState.KEYGUARD) {
-            return getMaxPanelHeightBypass();
-        } else {
-            return getMaxPanelHeightNonBypass();
-        }
-    }
-
-    private int getMaxPanelHeightNonBypass() {
-        int min = mStatusBarMinHeight;
-        if (!(mBarState == StatusBarState.KEYGUARD)
-                && mNotificationStackScroller.getNotGoneChildCount() == 0) {
-            int minHeight = (int) (mQsMinExpansionHeight + getOverExpansionAmount());
-            min = Math.max(min, minHeight);
-        }
-        int maxHeight;
-        if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted
-                || mPulsing) {
-            maxHeight = calculatePanelHeightQsExpanded();
-        } else {
-            maxHeight = calculatePanelHeightShade();
-        }
-        maxHeight = Math.max(maxHeight, min);
-        return maxHeight;
-    }
-
-    private int getMaxPanelHeightBypass() {
-        int position =
-                mClockPositionAlgorithm.getExpandedClockPosition()
-                        + mKeyguardStatusView.getHeight();
-        if (mNotificationStackScroller.getVisibleNotificationCount() != 0) {
-            position += mShelfHeight / 2.0f + mDarkIconSize / 2.0f;
-        }
-        return position;
-    }
-
-    public boolean isInSettings() {
-        return mQsExpanded;
-    }
-
-    public boolean isExpanding() {
-        return mIsExpanding;
-    }
-
-    @Override
-    protected void onHeightUpdated(float expandedHeight) {
-        if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
-            // Updating the clock position will set the top padding which might
-            // trigger a new panel height and re-position the clock.
-            // This is a circular dependency and should be avoided, otherwise we'll have
-            // a stack overflow.
-            if (mStackScrollerMeasuringPass > 2) {
-                if (DEBUG) Log.d(TAG, "Unstable notification panel height. Aborting.");
-            } else {
-                positionClockAndNotifications();
-            }
-        }
-        if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
-                && !mQsExpansionFromOverscroll) {
-            float t;
-            if (mKeyguardShowing) {
-
-                // On Keyguard, interpolate the QS expansion linearly to the panel expansion
-                t = expandedHeight / (getMaxPanelHeight());
-            } else {
-                // In Shade, interpolate linearly such that QS is closed whenever panel height is
-                // minimum QS expansion + minStackHeight
-                float
-                        panelHeightQsCollapsed =
-                        mNotificationStackScroller.getIntrinsicPadding()
-                                + mNotificationStackScroller.getLayoutMinHeight();
-                float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
-                t =
-                        (expandedHeight - panelHeightQsCollapsed) / (panelHeightQsExpanded
-                                - panelHeightQsCollapsed);
-            }
-            float
-                    targetHeight =
-                    mQsMinExpansionHeight + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
-            setQsExpansion(targetHeight);
-            mHomeControlsLayout.setTranslationY(targetHeight);
-        }
-        updateExpandedHeight(expandedHeight);
-        updateHeader();
-        updateNotificationTranslucency();
-        updatePanelExpanded();
-        updateGestureExclusionRect();
-        if (DEBUG) {
-            mView.invalidate();
-        }
-    }
-
-    private void updatePanelExpanded() {
-        boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown;
-        if (mPanelExpanded != isExpanded) {
-            mHeadsUpManager.setIsPanelExpanded(isExpanded);
-            mStatusBar.setPanelExpanded(isExpanded);
-            mPanelExpanded = isExpanded;
-        }
-    }
-
-    private int calculatePanelHeightShade() {
-        int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
-        int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin;
-        maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
-
-        if (mBarState == StatusBarState.KEYGUARD) {
-            int
-                    minKeyguardPanelBottom =
-                    mClockPositionAlgorithm.getExpandedClockPosition()
-                            + mKeyguardStatusView.getHeight()
-                            + mNotificationStackScroller.getIntrinsicContentHeight();
-            return Math.max(maxHeight, minKeyguardPanelBottom);
-        } else {
-            return maxHeight;
-        }
-    }
-
-    private int calculatePanelHeightQsExpanded() {
-        float
-                notificationHeight =
-                mNotificationStackScroller.getHeight()
-                        - mNotificationStackScroller.getEmptyBottomMargin()
-                        - mNotificationStackScroller.getTopPadding();
-
-        // When only empty shade view is visible in QS collapsed state, simulate that we would have
-        // it in expanded QS state as well so we don't run into troubles when fading the view in/out
-        // and expanding/collapsing the whole panel from/to quick settings.
-        if (mNotificationStackScroller.getNotGoneChildCount() == 0 && mShowEmptyShadeView) {
-            notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight();
-        }
-        int maxQsHeight = mQsMaxExpansionHeight;
-
-        if (mKeyguardShowing) {
-            maxQsHeight += mQsNotificationTopPadding;
-        }
-
-        // If an animation is changing the size of the QS panel, take the animated value.
-        if (mQsSizeChangeAnimator != null) {
-            maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
-        }
-        float totalHeight = Math.max(maxQsHeight,
-                mBarState == StatusBarState.KEYGUARD ? mClockPositionResult.stackScrollerPadding
-                        : 0) + notificationHeight
-                + mNotificationStackScroller.getTopPaddingOverflow();
-        if (totalHeight > mNotificationStackScroller.getHeight()) {
-            float
-                    fullyCollapsedHeight =
-                    maxQsHeight + mNotificationStackScroller.getLayoutMinHeight();
-            totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
-        }
-        return (int) totalHeight;
-    }
-
-    private void updateNotificationTranslucency() {
-        float alpha = 1f;
-        if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp
-                && !mHeadsUpManager.hasPinnedHeadsUp()) {
-            alpha = getFadeoutAlpha();
-        }
-        if (mBarState == StatusBarState.KEYGUARD && !mHintAnimationRunning
-                && !mKeyguardBypassController.getBypassEnabled()) {
-            alpha *= mClockPositionResult.clockAlpha;
-        }
-        mNotificationStackScroller.setAlpha(alpha);
-    }
-
-    private float getFadeoutAlpha() {
-        float alpha;
-        if (mQsMinExpansionHeight == 0) {
-            return 1.0f;
-        }
-        alpha = getExpandedHeight() / mQsMinExpansionHeight;
-        alpha = Math.max(0, Math.min(alpha, 1));
-        alpha = (float) Math.pow(alpha, 0.75);
-        return alpha;
-    }
-
-    @Override
-    protected float getOverExpansionAmount() {
-        return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
-    }
-
-    @Override
-    protected float getOverExpansionPixels() {
-        return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
-    }
-
-    /**
-     * Hides the header when notifications are colliding with it.
-     */
-    private void updateHeader() {
-        if (mBarState == StatusBarState.KEYGUARD) {
-            updateHeaderKeyguardAlpha();
-        }
-        updateQsExpansion();
-    }
-
-    protected float getHeaderTranslation() {
-        if (mBarState == StatusBarState.KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
-            return -mQs.getQsMinExpansionHeight();
-        }
-        float appearAmount = mNotificationStackScroller.calculateAppearFraction(mExpandedHeight);
-        float startHeight = -mQsExpansionHeight;
-        if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()
-                && mNotificationStackScroller.isPulseExpanding()) {
-            if (!mPulseExpansionHandler.isExpanding()
-                    && !mPulseExpansionHandler.getLeavingLockscreen()) {
-                // If we aborted the expansion we need to make sure the header doesn't reappear
-                // again after the header has animated away
-                appearAmount = 0;
-            } else {
-                appearAmount = mNotificationStackScroller.calculateAppearFractionBypass();
-            }
-            startHeight = -mQs.getQsMinExpansionHeight();
-            if (mNPVPluginManager != null) startHeight -= mNPVPluginManager.getHeight();
-        }
-        float translation = MathUtils.lerp(startHeight, 0, Math.min(1.0f, appearAmount))
-                + mExpandOffset;
-        return Math.min(0, translation);
-    }
-
-    /**
-     * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area)
-     * during swiping up
-     */
-    private float getKeyguardContentsAlpha() {
-        float alpha;
-        if (mBarState == StatusBarState.KEYGUARD) {
-
-            // When on Keyguard, we hide the header as soon as we expanded close enough to the
-            // header
-            alpha =
-                    getExpandedHeight() / (mKeyguardStatusBar.getHeight()
-                            + mNotificationsHeaderCollideDistance);
-        } else {
-
-            // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
-            // soon as we start translating the stack.
-            alpha = getExpandedHeight() / mKeyguardStatusBar.getHeight();
-        }
-        alpha = MathUtils.saturate(alpha);
-        alpha = (float) Math.pow(alpha, 0.75);
-        return alpha;
-    }
-
-    private void updateHeaderKeyguardAlpha() {
-        if (!mKeyguardShowing) {
-            return;
-        }
-        float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
-        float newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
-                * mKeyguardStatusBarAnimateAlpha;
-        newAlpha *= 1.0f - mKeyguardHeadsUpShowingAmount;
-        mKeyguardStatusBar.setAlpha(newAlpha);
-        boolean
-                hideForBypass =
-                mFirstBypassAttempt && mUpdateMonitor.shouldListenForFace()
-                        || mDelayShowingKeyguardStatusBar;
-        mKeyguardStatusBar.setVisibility(
-                newAlpha != 0f && !mDozing && !hideForBypass ? View.VISIBLE : View.INVISIBLE);
-    }
-
-    private void updateKeyguardBottomAreaAlpha() {
-        // There are two possible panel expansion behaviors:
-        // • User dragging up to unlock: we want to fade out as quick as possible
-        //   (ALPHA_EXPANSION_THRESHOLD) to avoid seeing the bouncer over the bottom area.
-        // • User tapping on lock screen: bouncer won't be visible but panel expansion will
-        //   change due to "unlock hint animation." In this case, fading out the bottom area
-        //   would also hide the message that says "swipe to unlock," we don't want to do that.
-        float expansionAlpha = MathUtils.map(
-                isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f,
-                getExpandedFraction());
-        float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction());
-        alpha *= mBottomAreaShadeAlpha;
-        mKeyguardBottomArea.setAffordanceAlpha(alpha);
-        mKeyguardBottomArea.setImportantForAccessibility(
-                alpha == 0f ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                        : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
-        View ambientIndicationContainer = mStatusBar.getAmbientIndicationContainer();
-        if (ambientIndicationContainer != null) {
-            ambientIndicationContainer.setAlpha(alpha);
-        }
-    }
-
-    /**
-     * Custom clock fades away when user drags up to unlock or pulls down quick settings.
-     *
-     * Updates alpha of custom clock to match the alpha of the KeyguardBottomArea. See
-     * {@link #updateKeyguardBottomAreaAlpha}.
-     */
-    private void updateBigClockAlpha() {
-        float expansionAlpha = MathUtils.map(
-                isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f,
-                getExpandedFraction());
-        float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction());
-        mBigClockContainer.setAlpha(alpha);
-    }
-
-    @Override
-    protected void onExpandingStarted() {
-        super.onExpandingStarted();
-        mNotificationStackScroller.onExpansionStarted();
-        mIsExpanding = true;
-        mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
-        if (mQsExpanded) {
-            onQsExpansionStarted();
-        }
-        // Since there are QS tiles in the header now, we need to make sure we start listening
-        // immediately so they can be up to date.
-        if (mQs == null) return;
-        mQs.setHeaderListening(true);
-    }
-
-    @Override
-    protected void onExpandingFinished() {
-        super.onExpandingFinished();
-        mNotificationStackScroller.onExpansionStopped();
-        mHeadsUpManager.onExpandingFinished();
-        mIsExpanding = false;
-        if (isFullyCollapsed()) {
-            DejankUtils.postAfterTraversal(new Runnable() {
-                @Override
-                public void run() {
-                    setListening(false);
-                }
-            });
-
-            // Workaround b/22639032: Make sure we invalidate something because else RenderThread
-            // thinks we are actually drawing a frame put in reality we don't, so RT doesn't go
-            // ahead with rendering and we jank.
-            mView.postOnAnimation(new Runnable() {
-                @Override
-                public void run() {
-                    mView.getParent().invalidateChild(mView, M_DUMMY_DIRTY_RECT);
-                }
-            });
-        } else {
-            setListening(true);
-        }
-        mQsExpandImmediate = false;
-        mNotificationStackScroller.setShouldShowShelfOnly(false);
-        mTwoFingerQsExpandPossible = false;
-        notifyListenersTrackingHeadsUp(null);
-        mExpandingFromHeadsUp = false;
-        setPanelScrimMinFraction(0.0f);
-    }
-
-    private void notifyListenersTrackingHeadsUp(ExpandableNotificationRow pickedChild) {
-        for (int i = 0; i < mTrackingHeadsUpListeners.size(); i++) {
-            Consumer<ExpandableNotificationRow> listener = mTrackingHeadsUpListeners.get(i);
-            listener.accept(pickedChild);
-        }
-    }
-
-    private void setListening(boolean listening) {
-        mKeyguardStatusBar.setListening(listening);
-        if (mQs == null) return;
-        mQs.setListening(listening);
-        if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening);
-    }
-
-    @Override
-    public void expand(boolean animate) {
-        super.expand(animate);
-        setListening(true);
-    }
-
-    @Override
-    protected void setOverExpansion(float overExpansion, boolean isPixels) {
-        if (mConflictingQsExpansionGesture || mQsExpandImmediate) {
-            return;
-        }
-        if (mBarState != StatusBarState.KEYGUARD) {
-            mNotificationStackScroller.setOnHeightChangedListener(null);
-            if (isPixels) {
-                mNotificationStackScroller.setOverScrolledPixels(overExpansion, true /* onTop */,
-                        false /* animate */);
-            } else {
-                mNotificationStackScroller.setOverScrollAmount(overExpansion, true /* onTop */,
-                        false /* animate */);
-            }
-            mNotificationStackScroller.setOnHeightChangedListener(mOnHeightChangedListener);
-        }
-    }
-
-    @Override
-    protected void onTrackingStarted() {
-        mFalsingManager.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
-        super.onTrackingStarted();
-        if (mQsFullyExpanded) {
-            mQsExpandImmediate = true;
-            mNotificationStackScroller.setShouldShowShelfOnly(true);
-        }
-        if (mBarState == StatusBarState.KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
-            mAffordanceHelper.animateHideLeftRightIcon();
-        }
-        mNotificationStackScroller.onPanelTrackingStarted();
-    }
-
-    @Override
-    protected void onTrackingStopped(boolean expand) {
-        mFalsingManager.onTrackingStopped();
-        super.onTrackingStopped(expand);
-        if (expand) {
-            mNotificationStackScroller.setOverScrolledPixels(0.0f, true /* onTop */,
-                    true /* animate */);
-        }
-        mNotificationStackScroller.onPanelTrackingStopped();
-        if (expand && (mBarState == StatusBarState.KEYGUARD
-                || mBarState == StatusBarState.SHADE_LOCKED)) {
-            if (!mHintAnimationRunning) {
-                mAffordanceHelper.reset(true);
-            }
-        }
-    }
-
-    private void updateMaxHeadsUpTranslation() {
-        mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight);
-    }
-
-    @Override
-    protected void startUnlockHintAnimation() {
-        if (mPowerManager.isPowerSaveMode()) {
-            onUnlockHintStarted();
-            onUnlockHintFinished();
-            return;
-        }
-        super.startUnlockHintAnimation();
-    }
-
-    @Override
-    protected void onUnlockHintFinished() {
-        super.onUnlockHintFinished();
-        mNotificationStackScroller.setUnlockHintRunning(false);
-    }
-
-    @Override
-    protected void onUnlockHintStarted() {
-        super.onUnlockHintStarted();
-        mNotificationStackScroller.setUnlockHintRunning(true);
-    }
-
-    @Override
-    protected float getPeekHeight() {
-        if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
-            return mNotificationStackScroller.getPeekHeight();
-        } else {
-            return mQsMinExpansionHeight;
-        }
-    }
-
-    @Override
-    protected boolean shouldUseDismissingAnimation() {
-        return mBarState != StatusBarState.SHADE && (mKeyguardStateController.canDismissLockScreen()
-                || !isTracking());
-    }
-
-    @Override
-    protected boolean fullyExpandedClearAllVisible() {
-        return mNotificationStackScroller.isFooterViewNotGone()
-                && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate;
-    }
-
-    @Override
-    protected boolean isClearAllVisible() {
-        return mNotificationStackScroller.isFooterViewContentVisible();
-    }
-
-    @Override
-    protected int getClearAllHeight() {
-        return mNotificationStackScroller.getFooterViewHeight();
-    }
-
-    @Override
-    protected boolean isTrackingBlocked() {
-        return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
-    }
-
-    public boolean isQsExpanded() {
-        return mQsExpanded;
-    }
-
-    public boolean isQsDetailShowing() {
-        return mQs.isShowingDetail();
-    }
-
-    public void closeQsDetail() {
-        mQs.closeDetail();
-    }
-
-    public boolean isLaunchTransitionFinished() {
-        return mIsLaunchTransitionFinished;
-    }
-
-    public boolean isLaunchTransitionRunning() {
-        return mIsLaunchTransitionRunning;
-    }
-
-    public void setLaunchTransitionEndRunnable(Runnable r) {
-        mLaunchAnimationEndRunnable = r;
-    }
-
-    private void updateDozingVisibilities(boolean animate) {
-        mKeyguardBottomArea.setDozing(mDozing, animate);
-        if (!mDozing && animate) {
-            animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-        }
-    }
-
-    @Override
-    public boolean isDozing() {
-        return mDozing;
-    }
-
-    public void showEmptyShadeView(boolean emptyShadeViewVisible) {
-        mShowEmptyShadeView = emptyShadeViewVisible;
-        updateEmptyShadeView();
-    }
-
-    private void updateEmptyShadeView() {
-        // Hide "No notifications" in QS.
-        mNotificationStackScroller.updateEmptyShadeView(mShowEmptyShadeView && !mQsExpanded);
-    }
-
-    public void setQsScrimEnabled(boolean qsScrimEnabled) {
-        boolean changed = mQsScrimEnabled != qsScrimEnabled;
-        mQsScrimEnabled = qsScrimEnabled;
-        if (changed) {
-            updateQsState();
-        }
-    }
-
-    public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
-        mKeyguardUserSwitcher = keyguardUserSwitcher;
-    }
-
-    public void onScreenTurningOn() {
-        mKeyguardStatusView.dozeTimeTick();
-    }
-
-    @Override
-    protected boolean onMiddleClicked() {
-        switch (mBarState) {
-            case StatusBarState.KEYGUARD:
-                if (!mDozingOnDown) {
-                    if (mKeyguardBypassController.getBypassEnabled()) {
-                        mUpdateMonitor.requestFaceAuth();
-                    } else {
-                        mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
-                                0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
-                        startUnlockHintAnimation();
-                    }
-                }
-                return true;
-            case StatusBarState.SHADE_LOCKED:
-                if (!mQsExpanded) {
-                    mStatusBarStateController.setState(StatusBarState.KEYGUARD);
-                }
-                return true;
-            case StatusBarState.SHADE:
-
-                // This gets called in the middle of the touch handling, where the state is still
-                // that we are tracking the panel. Collapse the panel after this is done.
-                mView.post(mPostCollapseRunnable);
-                return false;
-            default:
-                return true;
-        }
-    }
-
-    public void setPanelAlpha(int alpha, boolean animate) {
-        if (mPanelAlpha != alpha) {
-            mPanelAlpha = alpha;
-            PropertyAnimator.setProperty(mView, mPanelAlphaAnimator, alpha, alpha == 255
-                            ? mPanelAlphaInPropertiesAnimator : mPanelAlphaOutPropertiesAnimator,
-                    animate);
-        }
-    }
-
-    public void setPanelAlphaEndAction(Runnable r) {
-        mPanelAlphaEndAction = r;
-    }
-
-    private void updateKeyguardStatusBarForHeadsUp() {
-        boolean
-                showingKeyguardHeadsUp =
-                mKeyguardShowing && mHeadsUpAppearanceController.shouldBeVisible();
-        if (mShowingKeyguardHeadsUp != showingKeyguardHeadsUp) {
-            mShowingKeyguardHeadsUp = showingKeyguardHeadsUp;
-            if (mKeyguardShowing) {
-                PropertyAnimator.setProperty(mView, KEYGUARD_HEADS_UP_SHOWING_AMOUNT,
-                        showingKeyguardHeadsUp ? 1.0f : 0.0f, KEYGUARD_HUN_PROPERTIES,
-                        true /* animate */);
-            } else {
-                PropertyAnimator.applyImmediately(mView, KEYGUARD_HEADS_UP_SHOWING_AMOUNT, 0.0f);
-            }
-        }
-    }
-
-    private void setKeyguardHeadsUpShowingAmount(float amount) {
-        mKeyguardHeadsUpShowingAmount = amount;
-        updateHeaderKeyguardAlpha();
-    }
-
-    private float getKeyguardHeadsUpShowingAmount() {
-        return mKeyguardHeadsUpShowingAmount;
-    }
-
-    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
-        mHeadsUpAnimatingAway = headsUpAnimatingAway;
-        mNotificationStackScroller.setHeadsUpAnimatingAway(headsUpAnimatingAway);
-        updateHeadsUpVisibility();
-    }
-
-    private void updateHeadsUpVisibility() {
-        ((PhoneStatusBarView) mBar).setHeadsUpVisible(mHeadsUpAnimatingAway || mHeadsUpPinnedMode);
-    }
-
-    @Override
-    public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
-        super.setHeadsUpManager(headsUpManager);
-        mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
-                mNotificationStackScroller.getHeadsUpCallback(),
-                NotificationPanelViewController.this);
-    }
-
-    public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) {
-        if (pickedChild != null) {
-            notifyListenersTrackingHeadsUp(pickedChild);
-            mExpandingFromHeadsUp = true;
-        }
-        // otherwise we update the state when the expansion is finished
-    }
-
-    @Override
-    protected void onClosingFinished() {
-        super.onClosingFinished();
-        resetHorizontalPanelPosition();
-        setClosingWithAlphaFadeout(false);
-    }
-
-    private void setClosingWithAlphaFadeout(boolean closing) {
-        mClosingWithAlphaFadeOut = closing;
-        mNotificationStackScroller.forceNoOverlappingRendering(closing);
-    }
-
-    /**
-     * Updates the vertical position of the panel so it is positioned closer to the touch
-     * responsible for opening the panel.
-     *
-     * @param x the x-coordinate the touch event
-     */
-    protected void updateVerticalPanelPosition(float x) {
-        if (mNotificationStackScroller.getWidth() * 1.75f > mView.getWidth()) {
-            resetHorizontalPanelPosition();
-            return;
-        }
-        float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2;
-        float
-                rightMost =
-                mView.getWidth() - mPositionMinSideMargin
-                        - mNotificationStackScroller.getWidth() / 2;
-        if (Math.abs(x - mView.getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) {
-            x = mView.getWidth() / 2;
-        }
-        x = Math.min(rightMost, Math.max(leftMost, x));
-        float
-                center =
-                mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2;
-        setHorizontalPanelTranslation(x - center);
-    }
-
-    private void resetHorizontalPanelPosition() {
-        setHorizontalPanelTranslation(0f);
-    }
-
-    protected void setHorizontalPanelTranslation(float translation) {
-        mNotificationStackScroller.setTranslationX(translation);
-        mQsFrame.setTranslationX(translation);
-        int size = mVerticalTranslationListener.size();
-        for (int i = 0; i < size; i++) {
-            mVerticalTranslationListener.get(i).run();
-        }
-    }
-
-    protected void updateExpandedHeight(float expandedHeight) {
-        if (mTracking) {
-            mNotificationStackScroller.setExpandingVelocity(getCurrentExpandVelocity());
-        }
-        if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()) {
-            // The expandedHeight is always the full panel Height when bypassing
-            expandedHeight = getMaxPanelHeightNonBypass();
-        }
-        mNotificationStackScroller.setExpandedHeight(expandedHeight);
-        updateKeyguardBottomAreaAlpha();
-        updateBigClockAlpha();
-        updateStatusBarIcons();
-    }
-
-    /**
-     * @return whether the notifications are displayed full width and don't have any margins on
-     * the side.
-     */
-    public boolean isFullWidth() {
-        return mIsFullWidth;
-    }
-
-    private void updateStatusBarIcons() {
-        boolean
-                showIconsWhenExpanded =
-                (isPanelVisibleBecauseOfHeadsUp() || isFullWidth())
-                        && getExpandedHeight() < getOpeningHeight();
-        boolean noVisibleNotifications = true;
-        if (showIconsWhenExpanded && noVisibleNotifications && isOnKeyguard()) {
-            showIconsWhenExpanded = false;
-        }
-        if (showIconsWhenExpanded != mShowIconsWhenExpanded) {
-            mShowIconsWhenExpanded = showIconsWhenExpanded;
-            mCommandQueue.recomputeDisableFlags(mDisplayId, false);
-        }
-    }
-
-    private boolean isOnKeyguard() {
-        return mBarState == StatusBarState.KEYGUARD;
-    }
-
-    public void setPanelScrimMinFraction(float minFraction) {
-        mBar.panelScrimMinFractionChanged(minFraction);
-    }
-
-    public void clearNotificationEffects() {
-        mStatusBar.clearNotificationEffects();
-    }
-
-    @Override
-    protected boolean isPanelVisibleBecauseOfHeadsUp() {
-        return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
-                && mBarState == StatusBarState.SHADE;
-    }
-
-    public void launchCamera(boolean animate, int source) {
-        if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) {
-            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
-        } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
-            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
-        } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER) {
-            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER;
-        } else {
-
-            // Default.
-            mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
-        }
-
-        // If we are launching it when we are occluded already we don't want it to animate,
-        // nor setting these flags, since the occluded state doesn't change anymore, hence it's
-        // never reset.
-        if (!isFullyCollapsed()) {
-            setLaunchingAffordance(true);
-        } else {
-            animate = false;
-        }
-        mAffordanceHasPreview = mKeyguardBottomArea.getRightPreview() != null;
-        mAffordanceHelper.launchAffordance(
-                animate, mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
-    }
-
-    public void onAffordanceLaunchEnded() {
-        setLaunchingAffordance(false);
-    }
-
-    /**
-     * Set whether we are currently launching an affordance. This is currently only set when
-     * launched via a camera gesture.
-     */
-    private void setLaunchingAffordance(boolean launchingAffordance) {
-        mLaunchingAffordance = launchingAffordance;
-        mKeyguardAffordanceHelperCallback.getLeftIcon().setLaunchingAffordance(launchingAffordance);
-        mKeyguardAffordanceHelperCallback.getRightIcon().setLaunchingAffordance(
-                launchingAffordance);
-        mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
-        if (mAffordanceLaunchListener != null) {
-            mAffordanceLaunchListener.accept(launchingAffordance);
-        }
-    }
-
-    /**
-     * Return true when a bottom affordance is launching an occluded activity with a splash screen.
-     */
-    public boolean isLaunchingAffordanceWithPreview() {
-        return mLaunchingAffordance && mAffordanceHasPreview;
-    }
-
-    /**
-     * Whether the camera application can be launched for the camera launch gesture.
-     */
-    public boolean canCameraGestureBeLaunched() {
-        if (!mStatusBar.isCameraAllowedByAdmin()) {
-            return false;
-        }
-
-        ResolveInfo resolveInfo = mKeyguardBottomArea.resolveCameraIntent();
-        String
-                packageToLaunch =
-                (resolveInfo == null || resolveInfo.activityInfo == null) ? null
-                        : resolveInfo.activityInfo.packageName;
-        return packageToLaunch != null && (mBarState != StatusBarState.SHADE || !isForegroundApp(
-                packageToLaunch)) && !mAffordanceHelper.isSwipingInProgress();
-    }
-
-    /**
-     * Return true if the applications with the package name is running in foreground.
-     *
-     * @param pkgName application package name.
-     */
-    private boolean isForegroundApp(String pkgName) {
-        List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
-        return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
-    }
-
-    private void setGroupManager(NotificationGroupManager groupManager) {
-        mGroupManager = groupManager;
-    }
-
-    public boolean hideStatusBarIconsWhenExpanded() {
-        if (mLaunchingNotification) {
-            return mHideIconsDuringNotificationLaunch;
-        }
-        if (mHeadsUpAppearanceController != null
-                && mHeadsUpAppearanceController.shouldBeVisible()) {
-            return false;
-        }
-        return !isFullWidth() || !mShowIconsWhenExpanded;
-    }
-
-    private final FragmentListener mFragmentListener = new FragmentListener() {
-        @Override
-        public void onFragmentViewCreated(String tag, Fragment fragment) {
-            mQs = (QS) fragment;
-            mQs.setPanelView(mHeightListener);
-            mQs.setExpandClickListener(mOnClickListener);
-            mQs.setHeaderClickable(mQsExpansionEnabled);
-            updateQSPulseExpansion();
-            mQs.setOverscrolling(mStackScrollerOverscrolling);
-
-            // recompute internal state when qspanel height changes
-            mQs.getView().addOnLayoutChangeListener(
-                    (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                        final int height = bottom - top;
-                        final int oldHeight = oldBottom - oldTop;
-                        if (height != oldHeight) {
-                            mHeightListener.onQsHeightChanged();
-                        }
-                    });
-            mNotificationStackScroller.setQsContainer((ViewGroup) mQs.getView());
-            if (mQs instanceof QSFragment) {
-                mKeyguardStatusBar.setQSPanel(((QSFragment) mQs).getQsPanel());
-            }
-            updateQsExpansion();
-        }
-
-        @Override
-        public void onFragmentViewDestroyed(String tag, Fragment fragment) {
-            // Manual handling of fragment lifecycle is only required because this bridges
-            // non-fragment and fragment code. Once we are using a fragment for the notification
-            // panel, mQs will not need to be null cause it will be tied to the same lifecycle.
-            if (fragment == mQs) {
-                mQs = null;
-            }
-        }
-    };
-
-    @Override
-    public void setTouchAndAnimationDisabled(boolean disabled) {
-        super.setTouchAndAnimationDisabled(disabled);
-        if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
-            mAffordanceHelper.reset(false /* animate */);
-        }
-        mNotificationStackScroller.setAnimationsEnabled(!disabled);
-    }
-
-    /**
-     * Sets the dozing state.
-     *
-     * @param dozing              {@code true} when dozing.
-     * @param animate             if transition should be animated.
-     * @param wakeUpTouchLocation touch event location - if woken up by SLPI sensor.
-     */
-    public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) {
-        if (dozing == mDozing) return;
-        mView.setDozing(dozing);
-        mDozing = dozing;
-        mNotificationStackScroller.setDozing(mDozing, animate, wakeUpTouchLocation);
-        mKeyguardBottomArea.setDozing(mDozing, animate);
-
-        if (dozing) {
-            mBottomAreaShadeAlphaAnimator.cancel();
-        }
-
-        if (mBarState == StatusBarState.KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
-            updateDozingVisibilities(animate);
-        }
-
-        final float dozeAmount = dozing ? 1 : 0;
-        mStatusBarStateController.setDozeAmount(dozeAmount, animate);
-    }
-
-    public void setPulsing(boolean pulsing) {
-        mPulsing = pulsing;
-        final boolean
-                animatePulse =
-                !mDozeParameters.getDisplayNeedsBlanking() && mDozeParameters.getAlwaysOn();
-        if (animatePulse) {
-            mAnimateNextPositionUpdate = true;
-        }
-        // Do not animate the clock when waking up from a pulse.
-        // The height callback will take care of pushing the clock to the right position.
-        if (!mPulsing && !mDozing) {
-            mAnimateNextPositionUpdate = false;
-        }
-        mNotificationStackScroller.setPulsing(pulsing, animatePulse);
-        mKeyguardStatusView.setPulsing(pulsing);
-    }
-
-    public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) {
-        if (mAmbientIndicationBottomPadding != ambientIndicationBottomPadding) {
-            mAmbientIndicationBottomPadding = ambientIndicationBottomPadding;
-            mStatusBar.updateKeyguardMaxNotifications();
-        }
-    }
-
-    public void dozeTimeTick() {
-        mKeyguardBottomArea.dozeTimeTick();
-        mKeyguardStatusView.dozeTimeTick();
-        if (mInterpolatedDarkAmount > 0) {
-            positionClockAndNotifications();
-        }
-    }
-
-    public void setStatusAccessibilityImportance(int mode) {
-        mKeyguardStatusView.setImportantForAccessibility(mode);
-    }
-
-    /**
-     * TODO: this should be removed.
-     * It's not correct to pass this view forward because other classes will end up adding
-     * children to it. Theme will be out of sync.
-     *
-     * @return bottom area view
-     */
-    public KeyguardBottomAreaView getKeyguardBottomAreaView() {
-        return mKeyguardBottomArea;
-    }
-
-    public void setUserSetupComplete(boolean userSetupComplete) {
-        mUserSetupComplete = userSetupComplete;
-        mKeyguardBottomArea.setUserSetupComplete(userSetupComplete);
-    }
-
-    public void applyExpandAnimationParams(ExpandAnimationParameters params) {
-        mExpandOffset = params != null ? params.getTopChange() : 0;
-        updateQsExpansion();
-        if (params != null) {
-            boolean hideIcons = params.getProgress(
-                    ActivityLaunchAnimator.ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
-            if (hideIcons != mHideIconsDuringNotificationLaunch) {
-                mHideIconsDuringNotificationLaunch = hideIcons;
-                if (!hideIcons) {
-                    mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
-                }
-            }
-        }
-    }
-
-    public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
-        mTrackingHeadsUpListeners.add(listener);
-    }
-
-    public void removeTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
-        mTrackingHeadsUpListeners.remove(listener);
-    }
-
-    public void addVerticalTranslationListener(Runnable verticalTranslationListener) {
-        mVerticalTranslationListener.add(verticalTranslationListener);
-    }
-
-    public void removeVerticalTranslationListener(Runnable verticalTranslationListener) {
-        mVerticalTranslationListener.remove(verticalTranslationListener);
-    }
-
-    public void setHeadsUpAppearanceController(
-            HeadsUpAppearanceController headsUpAppearanceController) {
-        mHeadsUpAppearanceController = headsUpAppearanceController;
-    }
-
-    /**
-     * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
-     * security view of the bouncer.
-     */
-    public void onBouncerPreHideAnimation() {
-        setKeyguardStatusViewVisibility(mBarState, true /* keyguardFadingAway */,
-                false /* goingToFullShade */);
-    }
-
-    /**
-     * Do not let the user drag the shade up and down for the current touch session.
-     * This is necessary to avoid shade expansion while/after the bouncer is dismissed.
-     */
-    public void blockExpansionForCurrentTouch() {
-        mBlockingExpansionForCurrentTouch = mTracking;
-    }
-
-    @Override
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        super.dump(fd, pw, args);
-        pw.println("    gestureExclusionRect: " + calculateGestureExclusionRect());
-        if (mKeyguardStatusBar != null) {
-            mKeyguardStatusBar.dump(fd, pw, args);
-        }
-        if (mKeyguardStatusView != null) {
-            mKeyguardStatusView.dump(fd, pw, args);
-        }
-    }
-
-    public boolean hasActiveClearableNotifications() {
-        return mNotificationStackScroller.hasActiveClearableNotifications(ROWS_ALL);
-    }
-
-    private void updateShowEmptyShadeView() {
-        boolean
-                showEmptyShadeView =
-                mBarState != StatusBarState.KEYGUARD && !mEntryManager.hasActiveNotifications();
-        showEmptyShadeView(showEmptyShadeView);
-    }
-
-    public RemoteInputController.Delegate createRemoteInputDelegate() {
-        return mNotificationStackScroller.createDelegate();
-    }
-
-    public void updateNotificationViews() {
-        mNotificationStackScroller.updateSectionBoundaries();
-        mNotificationStackScroller.updateSpeedBumpIndex();
-        mNotificationStackScroller.updateFooter();
-        updateShowEmptyShadeView();
-        mNotificationStackScroller.updateIconAreaViews();
-    }
-
-    public void onUpdateRowStates() {
-        mNotificationStackScroller.onUpdateRowStates();
-    }
-
-    public boolean hasPulsingNotifications() {
-        return mNotificationStackScroller.hasPulsingNotifications();
-    }
-
-    public ActivatableNotificationView getActivatedChild() {
-        return mNotificationStackScroller.getActivatedChild();
-    }
-
-    public void setActivatedChild(ActivatableNotificationView o) {
-        mNotificationStackScroller.setActivatedChild(o);
-    }
-
-    public void runAfterAnimationFinished(Runnable r) {
-        mNotificationStackScroller.runAfterAnimationFinished(r);
-    }
-
-    public void setScrollingEnabled(boolean b) {
-        mNotificationStackScroller.setScrollingEnabled(b);
-    }
-
-    public void initDependencies(StatusBar statusBar, NotificationGroupManager groupManager,
-            NotificationShelf notificationShelf,
-            NotificationIconAreaController notificationIconAreaController,
-            ScrimController scrimController) {
-        setStatusBar(statusBar);
-        setGroupManager(mGroupManager);
-        mNotificationStackScroller.setNotificationPanelController(this);
-        mNotificationStackScroller.setIconAreaController(notificationIconAreaController);
-        mNotificationStackScroller.setStatusBar(statusBar);
-        mNotificationStackScroller.setGroupManager(groupManager);
-        mNotificationStackScroller.setShelf(notificationShelf);
-        mNotificationStackScroller.setScrimController(scrimController);
-        updateShowEmptyShadeView();
-    }
-
-    public void showTransientIndication(int id) {
-        mKeyguardIndicationController.showTransientIndication(id);
-    }
-
-    public void setOnReinflationListener(Runnable onReinflationListener) {
-        mOnReinflationListener = onReinflationListener;
-    }
-
-    public static boolean isQsSplitEnabled() {
-        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.QS_SPLIT_ENABLED, false);
-    }
-
-    public void setAlpha(float alpha) {
-        mView.setAlpha(alpha);
-    }
-
-    public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
-        return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
-                durationMs).setInterpolator(Interpolators.ALPHA_OUT).withLayer().withEndAction(
-                endAction);
-    }
-
-    public void resetViewGroupFade() {
-        ViewGroupFadeHelper.reset(mView);
-    }
-
-    public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
-        mView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
-    }
-
-    public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
-        mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
-    }
-
-    public MyOnHeadsUpChangedListener getOnHeadsUpChangedListener() {
-        return mOnHeadsUpChangedListener;
-    }
-
-    public int getHeight() {
-        return mView.getHeight();
-    }
-
-    public TextView getHeaderDebugInfo() {
-        return mView.findViewById(R.id.header_debug_info);
-    }
-
-    public void onThemeChanged() {
-        mConfigurationListener.onThemeChanged();
-    }
-
-    @Override
-    public OnLayoutChangeListener createLayoutChangeListener() {
-        return new OnLayoutChangeListener();
-    }
-
-    public void setEmptyDragAmount(float amount) {
-        mExpansionCallback.setEmptyDragAmount(amount);
-    }
-
-    @Override
-    protected TouchHandler createTouchHandler() {
-        return new TouchHandler() {
-            @Override
-            public boolean onInterceptTouchEvent(MotionEvent event) {
-                if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) {
-                    return false;
-                }
-                initDownStates(event);
-                // Do not let touches go to shade or QS if the bouncer is visible,
-                // but still let user swipe down to expand the panel, dismissing the bouncer.
-                if (mStatusBar.isBouncerShowing()) {
-                    return true;
-                }
-                if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
-                    mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
-                    mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
-                    return true;
-                }
-                if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
-                        && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
-                    return true;
-                }
-
-                if (!isFullyCollapsed() && onQsIntercept(event)) {
-                    return true;
-                }
-                return super.onInterceptTouchEvent(event);
-            }
-
-            @Override
-            public boolean onTouch(View v, MotionEvent event) {
-                if (mBlockTouches || (mQs != null && mQs.isCustomizing())) {
-                    return false;
-                }
-
-                // Do not allow panel expansion if bouncer is scrimmed, otherwise user would be able
-                // to pull down QS or expand the shade.
-                if (mStatusBar.isBouncerShowingScrimmed()) {
-                    return false;
-                }
-
-                // Make sure the next touch won't the blocked after the current ends.
-                if (event.getAction() == MotionEvent.ACTION_UP
-                        || event.getAction() == MotionEvent.ACTION_CANCEL) {
-                    mBlockingExpansionForCurrentTouch = false;
-                }
-                // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately
-                // without any ACTION_MOVE event.
-                // In such case, simply expand the panel instead of being stuck at the bottom bar.
-                if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) {
-                    expand(true /* animate */);
-                }
-                initDownStates(event);
-                if (!mIsExpanding && !shouldQuickSettingsIntercept(mDownX, mDownY, 0)
-                        && mPulseExpansionHandler.onTouchEvent(event)) {
-                    // We're expanding all the other ones shouldn't get this anymore
-                    return true;
-                }
-                if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
-                        && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
-                    mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
-                }
-                boolean handled = false;
-                if ((!mIsExpanding || mHintAnimationRunning) && !mQsExpanded
-                        && mBarState != StatusBarState.SHADE && !mDozing) {
-                    handled |= mAffordanceHelper.onTouchEvent(event);
-                }
-                if (mOnlyAffordanceInThisMotion) {
-                    return true;
-                }
-                handled |= mHeadsUpTouchHelper.onTouchEvent(event);
-
-                if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
-                    return true;
-                }
-                if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
-                    mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
-                    updateVerticalPanelPosition(event.getX());
-                    handled = true;
-                }
-                handled |= super.onTouch(v, event);
-                return !mDozing || mPulsing || handled;
-            }
-        };
-    }
-
-    @Override
-    protected PanelViewController.OnConfigurationChangedListener
-            createOnConfigurationChangedListener() {
-        return new OnConfigurationChangedListener();
-    }
-
-    private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
-        @Override
-        public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
-
-            // Block update if we are in quick settings and just the top padding changed
-            // (i.e. view == null).
-            if (view == null && mQsExpanded) {
-                return;
-            }
-            if (needsAnimation && mInterpolatedDarkAmount == 0) {
-                mAnimateNextPositionUpdate = true;
-            }
-            ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
-            ExpandableNotificationRow
-                    firstRow =
-                    firstChildNotGone instanceof ExpandableNotificationRow
-                            ? (ExpandableNotificationRow) firstChildNotGone : null;
-            if (firstRow != null && (view == firstRow || (firstRow.getNotificationParent()
-                    == firstRow))) {
-                requestScrollerTopPaddingUpdate(false /* animate */);
-            }
-            requestPanelHeightUpdate();
-        }
-
-        @Override
-        public void onReset(ExpandableView view) {
-        }
-    }
-
-    private class OnClickListener implements View.OnClickListener {
-        @Override
-        public void onClick(View v) {
-            onQsExpansionStarted();
-            if (mQsExpanded) {
-                flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
-                        true /* isClick */);
-            } else if (mQsExpansionEnabled) {
-                mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
-                flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
-                        true /* isClick */);
-            }
-        }
-    }
-
-    private class OnOverscrollTopChangedListener implements
-            NotificationStackScrollLayout.OnOverscrollTopChangedListener {
-        @Override
-        public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
-            cancelQsAnimation();
-            if (!mQsExpansionEnabled) {
-                amount = 0f;
-            }
-            float rounded = amount >= 1f ? amount : 0f;
-            setOverScrolling(rounded != 0f && isRubberbanded);
-            mQsExpansionFromOverscroll = rounded != 0f;
-            mLastOverscroll = rounded;
-            updateQsState();
-            setQsExpansion(mQsMinExpansionHeight + rounded);
-        }
-
-        @Override
-        public void flingTopOverscroll(float velocity, boolean open) {
-            mLastOverscroll = 0f;
-            mQsExpansionFromOverscroll = false;
-            setQsExpansion(mQsExpansionHeight);
-            flingSettings(!mQsExpansionEnabled && open ? 0f : velocity,
-                    open && mQsExpansionEnabled ? FLING_EXPAND : FLING_COLLAPSE, () -> {
-                        mStackScrollerOverscrolling = false;
-                        setOverScrolling(false);
-                        updateQsState();
-                    }, false /* isClick */);
-        }
-    }
-
-    private class DynamicPrivacyControlListener implements DynamicPrivacyController.Listener {
-        @Override
-        public void onDynamicPrivacyChanged() {
-            // Do not request animation when pulsing or waking up, otherwise the clock wiill be out
-            // of sync with the notification panel.
-            if (mLinearDarkAmount != 0) {
-                return;
-            }
-            mAnimateNextPositionUpdate = true;
-        }
-    }
-
-    private class KeyguardAffordanceHelperCallback implements KeyguardAffordanceHelper.Callback {
-        @Override
-        public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
-            boolean
-                    start =
-                    mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? rightPage
-                            : !rightPage;
-            mIsLaunchTransitionRunning = true;
-            mLaunchAnimationEndRunnable = null;
-            float displayDensity = mStatusBar.getDisplayDensity();
-            int lengthDp = Math.abs((int) (translation / displayDensity));
-            int velocityDp = Math.abs((int) (vel / displayDensity));
-            if (start) {
-                mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_DIALER, lengthDp, velocityDp);
-
-                mFalsingManager.onLeftAffordanceOn();
-                if (mFalsingManager.shouldEnforceBouncer()) {
-                    mStatusBar.executeRunnableDismissingKeyguard(
-                            () -> mKeyguardBottomArea.launchLeftAffordance(), null,
-                            true /* dismissShade */, false /* afterKeyguardGone */,
-                            true /* deferred */);
-                } else {
-                    mKeyguardBottomArea.launchLeftAffordance();
-                }
-            } else {
-                if (KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE.equals(
-                        mLastCameraLaunchSource)) {
-                    mLockscreenGestureLogger.write(
-                            MetricsEvent.ACTION_LS_CAMERA, lengthDp, velocityDp);
-                }
-                mFalsingManager.onCameraOn();
-                if (mFalsingManager.shouldEnforceBouncer()) {
-                    mStatusBar.executeRunnableDismissingKeyguard(
-                            () -> mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource), null,
-                            true /* dismissShade */, false /* afterKeyguardGone */,
-                            true /* deferred */);
-                } else {
-                    mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
-                }
-            }
-            mStatusBar.startLaunchTransitionTimeout();
-            mBlockTouches = true;
-        }
-
-        @Override
-        public void onAnimationToSideEnded() {
-            mIsLaunchTransitionRunning = false;
-            mIsLaunchTransitionFinished = true;
-            if (mLaunchAnimationEndRunnable != null) {
-                mLaunchAnimationEndRunnable.run();
-                mLaunchAnimationEndRunnable = null;
-            }
-            mStatusBar.readyForKeyguardDone();
-        }
-
-        @Override
-        public float getMaxTranslationDistance() {
-            return (float) Math.hypot(mView.getWidth(), getHeight());
-        }
-
-        @Override
-        public void onSwipingStarted(boolean rightIcon) {
-            mFalsingManager.onAffordanceSwipingStarted(rightIcon);
-            boolean
-                    camera =
-                    mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon
-                            : rightIcon;
-            if (camera) {
-                mKeyguardBottomArea.bindCameraPrewarmService();
-            }
-            mView.requestDisallowInterceptTouchEvent(true);
-            mOnlyAffordanceInThisMotion = true;
-            mQsTracking = false;
-        }
-
-        @Override
-        public void onSwipingAborted() {
-            mFalsingManager.onAffordanceSwipingAborted();
-            mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
-        }
-
-        @Override
-        public void onIconClicked(boolean rightIcon) {
-            if (mHintAnimationRunning) {
-                return;
-            }
-            mHintAnimationRunning = true;
-            mAffordanceHelper.startHintAnimation(rightIcon, () -> {
-                mHintAnimationRunning = false;
-                mStatusBar.onHintFinished();
-            });
-            rightIcon =
-                    mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon
-                            : rightIcon;
-            if (rightIcon) {
-                mStatusBar.onCameraHintStarted();
-            } else {
-                if (mKeyguardBottomArea.isLeftVoiceAssist()) {
-                    mStatusBar.onVoiceAssistHintStarted();
-                } else {
-                    mStatusBar.onPhoneHintStarted();
-                }
-            }
-        }
-
-        @Override
-        public KeyguardAffordanceView getLeftIcon() {
-            return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
-                    ? mKeyguardBottomArea.getRightView() : mKeyguardBottomArea.getLeftView();
-        }
-
-        @Override
-        public KeyguardAffordanceView getRightIcon() {
-            return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
-                    ? mKeyguardBottomArea.getLeftView() : mKeyguardBottomArea.getRightView();
-        }
-
-        @Override
-        public View getLeftPreview() {
-            return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
-                    ? mKeyguardBottomArea.getRightPreview() : mKeyguardBottomArea.getLeftPreview();
-        }
-
-        @Override
-        public View getRightPreview() {
-            return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
-                    ? mKeyguardBottomArea.getLeftPreview() : mKeyguardBottomArea.getRightPreview();
-        }
-
-        @Override
-        public float getAffordanceFalsingFactor() {
-            return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
-        }
-
-        @Override
-        public boolean needsAntiFalsing() {
-            return mBarState == StatusBarState.KEYGUARD;
-        }
-    }
-
-    private class OnEmptySpaceClickListener implements
-            NotificationStackScrollLayout.OnEmptySpaceClickListener {
-        @Override
-        public void onEmptySpaceClicked(float x, float y) {
-            onEmptySpaceClick(x);
-        }
-    }
-
-    private class MyOnHeadsUpChangedListener implements OnHeadsUpChangedListener {
-        @Override
-        public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
-            mNotificationStackScroller.setInHeadsUpPinnedMode(inPinnedMode);
-            if (inPinnedMode) {
-                mHeadsUpExistenceChangedRunnable.run();
-                updateNotificationTranslucency();
-            } else {
-                setHeadsUpAnimatingAway(true);
-                mNotificationStackScroller.runAfterAnimationFinished(
-                        mHeadsUpExistenceChangedRunnable);
-            }
-            updateGestureExclusionRect();
-            mHeadsUpPinnedMode = inPinnedMode;
-            updateHeadsUpVisibility();
-            updateKeyguardStatusBarForHeadsUp();
-        }
-
-        @Override
-        public void onHeadsUpPinned(NotificationEntry entry) {
-            if (!isOnKeyguard()) {
-                mNotificationStackScroller.generateHeadsUpAnimation(entry.getHeadsUpAnimationView(),
-                        true);
-            }
-        }
-
-        @Override
-        public void onHeadsUpUnPinned(NotificationEntry entry) {
-
-            // When we're unpinning the notification via active edge they remain heads-upped,
-            // we need to make sure that an animation happens in this case, otherwise the
-            // notification
-            // will stick to the top without any interaction.
-            if (isFullyCollapsed() && entry.isRowHeadsUp() && !isOnKeyguard()) {
-                mNotificationStackScroller.generateHeadsUpAnimation(
-                        entry.getHeadsUpAnimationView(), false);
-                entry.setHeadsUpIsVisible();
-            }
-        }
-
-        @Override
-        public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
-            mNotificationStackScroller.generateHeadsUpAnimation(entry, isHeadsUp);
-        }
-    }
-
-    private class HeightListener implements QS.HeightListener {
-        public void onQsHeightChanged() {
-            mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
-            if (mQsExpanded && mQsFullyExpanded) {
-                mQsExpansionHeight = mQsMaxExpansionHeight;
-                requestScrollerTopPaddingUpdate(false /* animate */);
-                requestPanelHeightUpdate();
-            }
-            if (mAccessibilityManager.isEnabled()) {
-                mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
-            }
-            mNotificationStackScroller.setMaxTopPadding(
-                    mQsMaxExpansionHeight + mQsNotificationTopPadding);
-        }
-    }
-
-    private class ZenModeControllerCallback implements ZenModeController.Callback {
-        @Override
-        public void onZenChanged(int zen) {
-            updateShowEmptyShadeView();
-        }
-    }
-
-    private class ConfigurationListener implements ConfigurationController.ConfigurationListener {
-        @Override
-        public void onDensityOrFontScaleChanged() {
-            updateShowEmptyShadeView();
-        }
-
-        @Override
-        public void onThemeChanged() {
-            final int themeResId = mView.getContext().getThemeResId();
-            if (mThemeResId == themeResId) {
-                return;
-            }
-            mThemeResId = themeResId;
-
-            reInflateViews();
-        }
-
-        @Override
-        public void onOverlayChanged() {
-            reInflateViews();
-        }
-
-        @Override
-        public void onUiModeChanged() {
-            reinflatePluginContainer();
-        }
-    }
-
-    private class StatusBarStateListener implements StateListener {
-        @Override
-        public void onStateChanged(int statusBarState) {
-            boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
-            boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
-            int oldState = mBarState;
-            boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD;
-            setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
-            setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
-
-            mBarState = statusBarState;
-            mKeyguardShowing = keyguardShowing;
-            if (mKeyguardShowing && isQsSplitEnabled()) {
-                mNotificationStackScroller.setVisibility(View.VISIBLE);
-                mQsFrame.setVisibility(View.VISIBLE);
-                mHomeControlsLayout.setVisibility(View.GONE);
-            }
-
-            if (oldState == StatusBarState.KEYGUARD && (goingToFullShade
-                    || statusBarState == StatusBarState.SHADE_LOCKED)) {
-                animateKeyguardStatusBarOut();
-                long
-                        delay =
-                        mBarState == StatusBarState.SHADE_LOCKED ? 0
-                                : mKeyguardStateController.calculateGoingToFullShadeDelay();
-                mQs.animateHeaderSlidingIn(delay);
-            } else if (oldState == StatusBarState.SHADE_LOCKED
-                    && statusBarState == StatusBarState.KEYGUARD) {
-                animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                mNotificationStackScroller.resetScrollPosition();
-                // Only animate header if the header is visible. If not, it will partially
-                // animate out
-                // the top of QS
-                if (!mQsExpanded) {
-                    mQs.animateHeaderSlidingOut();
-                }
-            } else {
-                mKeyguardStatusBar.setAlpha(1f);
-                mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
-                ((PhoneStatusBarView) mBar).maybeShowDivider(keyguardShowing);
-                if (keyguardShowing && oldState != mBarState) {
-                    if (mQs != null) {
-                        mQs.hideImmediately();
-                    }
-                }
-            }
-            updateKeyguardStatusBarForHeadsUp();
-            if (keyguardShowing) {
-                updateDozingVisibilities(false /* animate */);
-            }
-            // THe update needs to happen after the headerSlide in above, otherwise the translation
-            // would reset
-            updateQSPulseExpansion();
-            maybeAnimateBottomAreaAlpha();
-            resetHorizontalPanelPosition();
-            updateQsState();
-        }
-
-        @Override
-        public void onDozeAmountChanged(float linearAmount, float amount) {
-            mInterpolatedDarkAmount = amount;
-            mLinearDarkAmount = linearAmount;
-            mKeyguardStatusView.setDarkAmount(mInterpolatedDarkAmount);
-            mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
-            positionClockAndNotifications();
-        }
-    }
-
-    private class ExpansionCallback implements PulseExpansionHandler.ExpansionCallback {
-        public void setEmptyDragAmount(float amount) {
-            mEmptyDragAmount = amount * 0.2f;
-            positionClockAndNotifications();
-        }
-    }
-
-    private class OnAttachStateChangeListener implements View.OnAttachStateChangeListener {
-        @Override
-        public void onViewAttachedToWindow(View v) {
-            FragmentHostManager.get(mView).addTagListener(QS.TAG, mFragmentListener);
-            mStatusBarStateController.addCallback(mStatusBarStateListener);
-            mZenModeController.addCallback(mZenModeControllerCallback);
-            mConfigurationController.addCallback(mConfigurationListener);
-            mUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
-            // Theme might have changed between inflating this view and attaching it to the
-            // window, so
-            // force a call to onThemeChanged
-            mConfigurationListener.onThemeChanged();
-        }
-
-        @Override
-        public void onViewDetachedFromWindow(View v) {
-            FragmentHostManager.get(mView).removeTagListener(QS.TAG, mFragmentListener);
-            mStatusBarStateController.removeCallback(mStatusBarStateListener);
-            mZenModeController.removeCallback(mZenModeControllerCallback);
-            mConfigurationController.removeCallback(mConfigurationListener);
-            mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
-        }
-    }
-
-    private class OnLayoutChangeListener extends PanelViewController.OnLayoutChangeListener {
-
-        @Override
-        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
-                int oldTop, int oldRight, int oldBottom) {
-            DejankUtils.startDetectingBlockingIpcs("NVP#onLayout");
-            super.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom);
-            setIsFullWidth(mNotificationStackScroller.getWidth() == mView.getWidth());
-
-            // Update Clock Pivot
-            mKeyguardStatusView.setPivotX(mView.getWidth() / 2);
-            mKeyguardStatusView.setPivotY(
-                    (FONT_HEIGHT - CAP_HEIGHT) / 2048f * mKeyguardStatusView.getClockTextSize());
-
-            // Calculate quick setting heights.
-            int oldMaxHeight = mQsMaxExpansionHeight;
-            if (mQs != null) {
-                mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
-                if (mNPVPluginManager != null) {
-                    mNPVPluginManager.setYOffset(mQsMinExpansionHeight);
-                    mQsMinExpansionHeight += mNPVPluginManager.getHeight();
-                }
-                mQsMaxExpansionHeight = mQs.getDesiredHeight();
-                mNotificationStackScroller.setMaxTopPadding(
-                        mQsMaxExpansionHeight + mQsNotificationTopPadding);
-            }
-            positionClockAndNotifications();
-            if (mQsExpanded && mQsFullyExpanded) {
-                mQsExpansionHeight = mQsMaxExpansionHeight;
-                requestScrollerTopPaddingUpdate(false /* animate */);
-                requestPanelHeightUpdate();
-
-                // Size has changed, start an animation.
-                if (mQsMaxExpansionHeight != oldMaxHeight) {
-                    startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
-                }
-            } else if (!mQsExpanded) {
-                setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
-            }
-            updateExpandedHeight(getExpandedHeight());
-            updateHeader();
-
-            // If we are running a size change animation, the animation takes care of the height of
-            // the container. However, if we are not animating, we always need to make the QS
-            // container
-            // the desired height so when closing the QS detail, it stays smaller after the size
-            // change
-            // animation is finished but the detail view is still being animated away (this
-            // animation
-            // takes longer than the size change animation).
-            if (mQsSizeChangeAnimator == null && mQs != null) {
-                mQs.setHeightOverride(mQs.getDesiredHeight());
-            }
-            updateMaxHeadsUpTranslation();
-            updateGestureExclusionRect();
-            if (mExpandAfterLayoutRunnable != null) {
-                mExpandAfterLayoutRunnable.run();
-                mExpandAfterLayoutRunnable = null;
-            }
-            DejankUtils.stopDetectingBlockingIpcs("NVP#onLayout");
-        }
-    }
-
-    private class DebugDrawable extends Drawable {
-
-        @Override
-        public void draw(Canvas canvas) {
-            Paint p = new Paint();
-            p.setColor(Color.RED);
-            p.setStrokeWidth(2);
-            p.setStyle(Paint.Style.STROKE);
-            canvas.drawLine(0, getMaxPanelHeight(), mView.getWidth(), getMaxPanelHeight(), p);
-            p.setColor(Color.BLUE);
-            canvas.drawLine(0, getExpandedHeight(), mView.getWidth(), getExpandedHeight(), p);
-            p.setColor(Color.GREEN);
-            canvas.drawLine(0, calculatePanelHeightQsExpanded(), mView.getWidth(),
-                    calculatePanelHeightQsExpanded(), p);
-            p.setColor(Color.YELLOW);
-            canvas.drawLine(0, calculatePanelHeightShade(), mView.getWidth(),
-                    calculatePanelHeightShade(), p);
-            p.setColor(Color.MAGENTA);
-            canvas.drawLine(
-                    0, calculateQsTopPadding(), mView.getWidth(), calculateQsTopPadding(), p);
-            p.setColor(Color.CYAN);
-            canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(),
-                    mNotificationStackScroller.getTopPadding(), p);
-            p.setColor(Color.GRAY);
-            canvas.drawLine(0, mClockPositionResult.clockY, mView.getWidth(),
-                    mClockPositionResult.clockY, p);
-        }
-
-        @Override
-        public void setAlpha(int alpha) {
-
-        }
-
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) {
-
-        }
-
-        @Override
-        public int getOpacity() {
-            return 0;
-        }
-    }
-
-    private class OnConfigurationChangedListener extends
-            PanelViewController.OnConfigurationChangedListener {
-        @Override
-        public void onConfigurationChanged(Configuration newConfig) {
-            super.onConfigurationChanged(newConfig);
-            mAffordanceHelper.onConfigurationChanged();
-            if (newConfig.orientation != mLastOrientation) {
-                resetHorizontalPanelPosition();
-            }
-            mLastOrientation = newConfig.orientation;
-        }
-    }
-
-    private class OnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener {
-        public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
-            mNavigationBarBottomHeight = insets.getStableInsetBottom();
-            updateMaxHeadsUpTranslation();
-            return insets;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index 8d8c8da..063d00b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -43,7 +43,7 @@
     public static final int STATE_OPENING = 1;
     public static final int STATE_OPEN = 2;
 
-    PanelViewController mPanel;
+    PanelView mPanel;
     private int mState = STATE_CLOSED;
     private boolean mTracking;
 
@@ -83,8 +83,7 @@
         super.onFinishInflate();
     }
 
-    /** Set the PanelViewController */
-    public void setPanel(PanelViewController pv) {
+    public void setPanel(PanelView pv) {
         mPanel = pv;
         pv.setBar(this);
     }
@@ -97,7 +96,7 @@
         setImportantForAccessibility(important);
         updateVisibility();
 
-        if (mPanel != null) mPanel.getView().setImportantForAccessibility(important);
+        if (mPanel != null) mPanel.setImportantForAccessibility(important);
     }
 
     public float getExpansionFraction() {
@@ -109,7 +108,7 @@
     }
 
     protected void updateVisibility() {
-        mPanel.getView().setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
+        mPanel.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
     }
 
     protected boolean shouldPanelBeVisible() {
@@ -132,7 +131,7 @@
         }
 
         if (event.getAction() == MotionEvent.ACTION_DOWN) {
-            final PanelViewController panel = mPanel;
+            final PanelView panel = mPanel;
             if (panel == null) {
                 // panel is not there, so we'll eat the gesture
                 Log.v(TAG, String.format("onTouch: no panel for touch at (%d,%d)",
@@ -150,7 +149,7 @@
                 return true;
             }
         }
-        return mPanel == null || mPanel.getView().dispatchTouchEvent(event);
+        return mPanel == null || mPanel.onTouchEvent(event);
     }
 
     public abstract void panelScrimMinFractionChanged(float minFraction);
@@ -164,7 +163,7 @@
         boolean fullyClosed = true;
         boolean fullyOpened = false;
         if (SPEW) LOG("panelExpansionChanged: start state=%d", mState);
-        PanelViewController pv = mPanel;
+        PanelView pv = mPanel;
         mExpanded = expanded;
         mPanelFraction = frac;
         updateVisibility();
@@ -193,7 +192,7 @@
 
     public void collapsePanel(boolean animate, boolean delayed, float speedUpFactor) {
         boolean waiting = false;
-        PanelViewController pv = mPanel;
+        PanelView pv = mPanel;
         if (animate && !pv.isFullyCollapsed()) {
             pv.collapse(delayed, speedUpFactor);
             waiting = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 2719a32..cd56d06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -16,62 +16,1255 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.InputDevice;
 import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewTreeObserver;
+import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.LatencyTracker;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
 public abstract class PanelView extends FrameLayout {
     public static final boolean DEBUG = PanelBar.DEBUG;
     public static final String TAG = PanelView.class.getSimpleName();
-    private PanelViewController.TouchHandler mTouchHandler;
+    private static final int INITIAL_OPENING_PEEK_DURATION = 200;
+    private static final int PEEK_ANIMATION_DURATION = 360;
+    private static final int NO_FIXED_DURATION = -1;
+    protected long mDownTime;
+    protected boolean mTouchSlopExceededBeforeDown;
+    private float mMinExpandHeight;
+    private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+    private boolean mPanelUpdateWhenAnimatorEnds;
+    private boolean mVibrateOnOpening;
+    protected boolean mLaunchingNotification;
+    private int mFixedDuration = NO_FIXED_DURATION;
+    protected ArrayList<PanelExpansionListener> mExpansionListeners = new ArrayList<>();
+
+    private final void logf(String fmt, Object... args) {
+        Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
+    }
 
     protected StatusBar mStatusBar;
     protected HeadsUpManagerPhone mHeadsUpManager;
 
+    private float mPeekHeight;
+    private float mHintDistance;
+    private float mInitialOffsetOnTouch;
+    private boolean mCollapsedAndHeadsUpOnDown;
+    private float mExpandedFraction = 0;
+    protected float mExpandedHeight = 0;
+    private boolean mPanelClosedOnDown;
+    private boolean mHasLayoutedSinceDown;
+    private float mUpdateFlingVelocity;
+    private boolean mUpdateFlingOnLayout;
+    private boolean mPeekTouching;
+    private boolean mJustPeeked;
+    private boolean mClosing;
+    protected boolean mTracking;
+    private boolean mTouchSlopExceeded;
+    private int mTrackingPointer;
     protected int mTouchSlop;
+    protected boolean mHintAnimationRunning;
+    private boolean mOverExpandedBeforeFling;
+    private boolean mTouchAboveFalsingThreshold;
+    private int mUnlockFalsingThreshold;
+    private boolean mTouchStartedInEmptyArea;
+    private boolean mMotionAborted;
+    private boolean mUpwardsWhenTresholdReached;
+    private boolean mAnimatingOnDown;
 
+    private ValueAnimator mHeightAnimator;
+    private ObjectAnimator mPeekAnimator;
+    private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+    private FlingAnimationUtils mFlingAnimationUtils;
+    private FlingAnimationUtils mFlingAnimationUtilsClosing;
+    private FlingAnimationUtils mFlingAnimationUtilsDismissing;
+    private final FalsingManager mFalsingManager;
+    private final DozeLog mDozeLog;
+    private final VibratorHelper mVibratorHelper;
+
+    /**
+     * Whether an instant expand request is currently pending and we are just waiting for layout.
+     */
+    private boolean mInstantExpanding;
+    private boolean mAnimateAfterExpanding;
+
+    PanelBar mBar;
+
+    private String mViewName;
+    private float mInitialTouchY;
+    private float mInitialTouchX;
+    private boolean mTouchDisabled;
+
+    /**
+     * Whether or not the PanelView can be expanded or collapsed with a drag.
+     */
+    private boolean mNotificationsDragEnabled;
+
+    private Interpolator mBounceInterpolator;
     protected KeyguardBottomAreaView mKeyguardBottomArea;
-    private OnConfigurationChangedListener mOnConfigurationChangedListener;
 
-    public PanelView(Context context) {
-        super(context);
+    /**
+     * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
+     */
+    private float mNextCollapseSpeedUpFactor = 1.0f;
+
+    protected boolean mExpanding;
+    private boolean mGestureWaitForTouchSlop;
+    private boolean mIgnoreXTouchSlop;
+    private boolean mExpandLatencyTracking;
+    protected final KeyguardStateController mKeyguardStateController;
+    protected final SysuiStatusBarStateController mStatusBarStateController;
+
+    protected void onExpandingFinished() {
+        mBar.onExpandingFinished();
     }
 
-    public PanelView(Context context, AttributeSet attrs) {
+    protected void onExpandingStarted() {
+    }
+
+    private void notifyExpandingStarted() {
+        if (!mExpanding) {
+            mExpanding = true;
+            onExpandingStarted();
+        }
+    }
+
+    protected final void notifyExpandingFinished() {
+        endClosing();
+        if (mExpanding) {
+            mExpanding = false;
+            onExpandingFinished();
+        }
+    }
+
+    private void runPeekAnimation(long duration, float peekHeight, boolean collapseWhenFinished) {
+        mPeekHeight = peekHeight;
+        if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
+        if (mHeightAnimator != null) {
+            return;
+        }
+        if (mPeekAnimator != null) {
+            mPeekAnimator.cancel();
+        }
+        mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight)
+                .setDuration(duration);
+        mPeekAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+        mPeekAnimator.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCancelled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mPeekAnimator = null;
+                if (!mCancelled && collapseWhenFinished) {
+                    postOnAnimation(mPostCollapseRunnable);
+                }
+
+            }
+        });
+        notifyExpandingStarted();
+        mPeekAnimator.start();
+        mJustPeeked = true;
+    }
+
+    public PanelView(Context context, AttributeSet attrs, FalsingManager falsingManager,
+            DozeLog dozeLog, KeyguardStateController keyguardStateController,
+            SysuiStatusBarStateController statusBarStateController) {
         super(context, attrs);
+        mKeyguardStateController = keyguardStateController;
+        mStatusBarStateController = statusBarStateController;
+        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+        mFlingAnimationUtils = new FlingAnimationUtils(displayMetrics,
+                0.6f /* maxLengthSeconds */, 0.6f /* speedUpFactor */);
+        mFlingAnimationUtilsClosing = new FlingAnimationUtils(displayMetrics,
+                0.5f /* maxLengthSeconds */, 0.6f /* speedUpFactor */);
+        mFlingAnimationUtilsDismissing = new FlingAnimationUtils(displayMetrics,
+                0.5f /* maxLengthSeconds */, 0.2f /* speedUpFactor */, 0.6f /* x2 */,
+                0.84f /* y2 */);
+        mBounceInterpolator = new BounceInterpolator();
+        mFalsingManager = falsingManager;
+        mDozeLog = dozeLog;
+        mNotificationsDragEnabled =
+                getResources().getBoolean(R.bool.config_enableNotificationShadeDrag);
+        mVibratorHelper = Dependency.get(VibratorHelper.class);
+        mVibrateOnOpening = mContext.getResources().getBoolean(
+                R.bool.config_vibrateOnIconAnimation);
     }
 
-    public PanelView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
+    protected void loadDimens() {
+        final Resources res = getContext().getResources();
+        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
+        mTouchSlop = configuration.getScaledTouchSlop();
+        mHintDistance = res.getDimension(R.dimen.hint_move_distance);
+        mUnlockFalsingThreshold = res.getDimensionPixelSize(R.dimen.unlock_falsing_threshold);
     }
 
-    public PanelView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
+    private void addMovement(MotionEvent event) {
+        // Add movement to velocity tracker using raw screen X and Y coordinates instead
+        // of window coordinates because the window frame may be moving at the same time.
+        float deltaX = event.getRawX() - event.getX();
+        float deltaY = event.getRawY() - event.getY();
+        event.offsetLocation(deltaX, deltaY);
+        mVelocityTracker.addMovement(event);
+        event.offsetLocation(-deltaX, -deltaY);
     }
 
-    public void setOnTouchListener(PanelViewController.TouchHandler touchHandler) {
-        super.setOnTouchListener(touchHandler);
-        mTouchHandler = touchHandler;
+    public void setTouchAndAnimationDisabled(boolean disabled) {
+        mTouchDisabled = disabled;
+        if (mTouchDisabled) {
+            cancelHeightAnimator();
+            if (mTracking) {
+                onTrackingStopped(true /* expanded */);
+            }
+            notifyExpandingFinished();
+        }
     }
 
-    public void setOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
-        mOnConfigurationChangedListener = listener;
+    public void startExpandLatencyTracking() {
+        if (LatencyTracker.isEnabled(mContext)) {
+            LatencyTracker.getInstance(mContext).onActionStart(
+                    LatencyTracker.ACTION_EXPAND_PANEL);
+            mExpandLatencyTracking = true;
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mInstantExpanding
+                || (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL)
+                || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
+            return false;
+        }
+
+        // If dragging should not expand the notifications shade, then return false.
+        if (!mNotificationsDragEnabled) {
+            if (mTracking) {
+                // Turn off tracking if it's on or the shade can get stuck in the down position.
+                onTrackingStopped(true /* expand */);
+            }
+            return false;
+        }
+
+        // On expanding, single mouse click expands the panel instead of dragging.
+        if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+            if (event.getAction() == MotionEvent.ACTION_UP) {
+                expand(true);
+            }
+            return true;
+        }
+
+        /*
+         * We capture touch events here and update the expand height here in case according to
+         * the users fingers. This also handles multi-touch.
+         *
+         * If the user just clicks shortly, we show a quick peek of the shade.
+         *
+         * Flinging is also enabled in order to open or close the shade.
+         */
+
+        int pointerIndex = event.findPointerIndex(mTrackingPointer);
+        if (pointerIndex < 0) {
+            pointerIndex = 0;
+            mTrackingPointer = event.getPointerId(pointerIndex);
+        }
+        final float x = event.getX(pointerIndex);
+        final float y = event.getY(pointerIndex);
+
+        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
+            mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y);
+        }
+
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
+                mJustPeeked = false;
+                mMinExpandHeight = 0.0f;
+                mPanelClosedOnDown = isFullyCollapsed();
+                mHasLayoutedSinceDown = false;
+                mUpdateFlingOnLayout = false;
+                mMotionAborted = false;
+                mPeekTouching = mPanelClosedOnDown;
+                mDownTime = SystemClock.uptimeMillis();
+                mTouchAboveFalsingThreshold = false;
+                mCollapsedAndHeadsUpOnDown = isFullyCollapsed()
+                        && mHeadsUpManager.hasPinnedHeadsUp();
+                addMovement(event);
+                if (!mGestureWaitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning)
+                        || mPeekAnimator != null) {
+                    mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning)
+                            || mPeekAnimator != null || mTouchSlopExceededBeforeDown;
+                    cancelHeightAnimator();
+                    cancelPeek();
+                    onTrackingStarted();
+                }
+                if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()
+                        && !mStatusBar.isBouncerShowing()) {
+                    startOpening(event);
+                }
+                break;
+
+            case MotionEvent.ACTION_POINTER_UP:
+                final int upPointer = event.getPointerId(event.getActionIndex());
+                if (mTrackingPointer == upPointer) {
+                    // gesture is ongoing, find a new pointer to track
+                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+                    final float newY = event.getY(newIndex);
+                    final float newX = event.getX(newIndex);
+                    mTrackingPointer = event.getPointerId(newIndex);
+                    startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
+                }
+                break;
+            case MotionEvent.ACTION_POINTER_DOWN:
+                if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+                    mMotionAborted = true;
+                    endMotionEvent(event, x, y, true /* forceCancel */);
+                    return false;
+                }
+                break;
+            case MotionEvent.ACTION_MOVE:
+                addMovement(event);
+                float h = y - mInitialTouchY;
+
+                // If the panel was collapsed when touching, we only need to check for the
+                // y-component of the gesture, as we have no conflicting horizontal gesture.
+                if (Math.abs(h) > mTouchSlop
+                        && (Math.abs(h) > Math.abs(x - mInitialTouchX)
+                        || mIgnoreXTouchSlop)) {
+                    mTouchSlopExceeded = true;
+                    if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
+                        if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
+                            startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
+                            h = 0;
+                        }
+                        cancelHeightAnimator();
+                        onTrackingStarted();
+                    }
+                }
+                float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
+                if (newHeight > mPeekHeight) {
+                    if (mPeekAnimator != null) {
+                        mPeekAnimator.cancel();
+                    }
+                    mJustPeeked = false;
+                } else if (mPeekAnimator == null && mJustPeeked) {
+                    // The initial peek has finished, but we haven't dragged as far yet, lets
+                    // speed it up by starting at the peek height.
+                    mInitialOffsetOnTouch = mExpandedHeight;
+                    mInitialTouchY = y;
+                    mMinExpandHeight = mExpandedHeight;
+                    mJustPeeked = false;
+                }
+                newHeight = Math.max(newHeight, mMinExpandHeight);
+                if (-h >= getFalsingThreshold()) {
+                    mTouchAboveFalsingThreshold = true;
+                    mUpwardsWhenTresholdReached = isDirectionUpwards(x, y);
+                }
+                if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) &&
+                        !isTrackingBlocked()) {
+                    setExpandedHeightInternal(newHeight);
+                }
+                break;
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                addMovement(event);
+                endMotionEvent(event, x, y, false /* forceCancel */);
+                break;
+        }
+        return !mGestureWaitForTouchSlop || mTracking;
+    }
+
+    private void startOpening(MotionEvent event) {
+        runPeekAnimation(INITIAL_OPENING_PEEK_DURATION, getOpeningHeight(),
+                false /* collapseWhenFinished */);
+        notifyBarPanelExpansionChanged();
+        maybeVibrateOnOpening();
+
+        //TODO: keyguard opens QS a different way; log that too?
+
+        // Log the position of the swipe that opened the panel
+        float width = mStatusBar.getDisplayWidth();
+        float height = mStatusBar.getDisplayHeight();
+        int rot = mStatusBar.getRotation();
+
+        mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND,
+                (int) (event.getX() / width * 100),
+                (int) (event.getY() / height * 100),
+                rot);
+    }
+
+    protected void maybeVibrateOnOpening() {
+        if (mVibrateOnOpening) {
+            mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+        }
+    }
+
+    protected abstract float getOpeningHeight();
+
+    /**
+     * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
+     * horizontal direction
+     */
+    private boolean isDirectionUpwards(float x, float y) {
+        float xDiff = x - mInitialTouchX;
+        float yDiff = y - mInitialTouchY;
+        if (yDiff >= 0) {
+            return false;
+        }
+        return Math.abs(yDiff) >= Math.abs(xDiff);
+    }
+
+    protected void startExpandingFromPeek() {
+        mStatusBar.handlePeekToExpandTransistion();
+    }
+
+    protected void startExpandMotion(float newX, float newY, boolean startTracking,
+            float expandedHeight) {
+        mInitialOffsetOnTouch = expandedHeight;
+        mInitialTouchY = newY;
+        mInitialTouchX = newX;
+        if (startTracking) {
+            mTouchSlopExceeded = true;
+            setExpandedHeight(mInitialOffsetOnTouch);
+            onTrackingStarted();
+        }
+    }
+
+    private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
+        mTrackingPointer = -1;
+        if ((mTracking && mTouchSlopExceeded)
+                || Math.abs(x - mInitialTouchX) > mTouchSlop
+                || Math.abs(y - mInitialTouchY) > mTouchSlop
+                || event.getActionMasked() == MotionEvent.ACTION_CANCEL
+                || forceCancel) {
+            mVelocityTracker.computeCurrentVelocity(1000);
+            float vel = mVelocityTracker.getYVelocity();
+            float vectorVel = (float) Math.hypot(
+                    mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+
+            boolean expand = flingExpands(vel, vectorVel, x, y)
+                    || event.getActionMasked() == MotionEvent.ACTION_CANCEL
+                    || forceCancel;
+            mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
+                    mStatusBar.isFalsingThresholdNeeded(),
+                    mStatusBar.isWakeUpComingFromTouch());
+                    // Log collapse gesture if on lock screen.
+                    if (!expand && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+                        float displayDensity = mStatusBar.getDisplayDensity();
+                        int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity);
+                        int velocityDp = (int) Math.abs(vel / displayDensity);
+                        mLockscreenGestureLogger.write(
+                                MetricsEvent.ACTION_LS_UNLOCK,
+                                heightDp, velocityDp);
+                    }
+            fling(vel, expand, isFalseTouch(x, y));
+            onTrackingStopped(expand);
+            mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
+            if (mUpdateFlingOnLayout) {
+                mUpdateFlingVelocity = vel;
+            }
+        } else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking
+                && !mStatusBar.isBouncerShowing()
+                && !mKeyguardStateController.isKeyguardFadingAway()) {
+            long timePassed = SystemClock.uptimeMillis() - mDownTime;
+            if (timePassed < ViewConfiguration.getLongPressTimeout()) {
+                // Lets show the user that he can actually expand the panel
+                runPeekAnimation(PEEK_ANIMATION_DURATION, getPeekHeight(), true /* collapseWhenFinished */);
+            } else {
+                // We need to collapse the panel since we peeked to the small height.
+                postOnAnimation(mPostCollapseRunnable);
+            }
+        } else if (!mStatusBar.isBouncerShowing()) {
+            boolean expands = onEmptySpaceClick(mInitialTouchX);
+            onTrackingStopped(expands);
+        }
+
+        mVelocityTracker.clear();
+        mPeekTouching = false;
+    }
+
+    protected float getCurrentExpandVelocity() {
+        mVelocityTracker.computeCurrentVelocity(1000);
+        return mVelocityTracker.getYVelocity();
+    }
+
+    private int getFalsingThreshold() {
+        float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+        return (int) (mUnlockFalsingThreshold * factor);
+    }
+
+    protected abstract boolean shouldGestureWaitForTouchSlop();
+
+    protected abstract boolean shouldGestureIgnoreXTouchSlop(float x, float y);
+
+    protected void onTrackingStopped(boolean expand) {
+        mTracking = false;
+        mBar.onTrackingStopped(expand);
+        notifyBarPanelExpansionChanged();
+    }
+
+    protected void onTrackingStarted() {
+        endClosing();
+        mTracking = true;
+        mBar.onTrackingStarted();
+        notifyExpandingStarted();
+        notifyBarPanelExpansionChanged();
     }
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
-        return mTouchHandler.onInterceptTouchEvent(event);
+        if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled
+                || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
+            return false;
+        }
+
+        /*
+         * If the user drags anywhere inside the panel we intercept it if the movement is
+         * upwards. This allows closing the shade from anywhere inside the panel.
+         *
+         * We only do this if the current content is scrolled to the bottom,
+         * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling gesture
+         * possible.
+         */
+        int pointerIndex = event.findPointerIndex(mTrackingPointer);
+        if (pointerIndex < 0) {
+            pointerIndex = 0;
+            mTrackingPointer = event.getPointerId(pointerIndex);
+        }
+        final float x = event.getX(pointerIndex);
+        final float y = event.getY(pointerIndex);
+        boolean scrolledToBottom = isScrolledToBottom();
+
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                mStatusBar.userActivity();
+                mAnimatingOnDown = mHeightAnimator != null;
+                mMinExpandHeight = 0.0f;
+                mDownTime = SystemClock.uptimeMillis();
+                if (mAnimatingOnDown && mClosing && !mHintAnimationRunning
+                        || mPeekAnimator != null) {
+                    cancelHeightAnimator();
+                    cancelPeek();
+                    mTouchSlopExceeded = true;
+                    return true;
+                }
+                mInitialTouchY = y;
+                mInitialTouchX = x;
+                mTouchStartedInEmptyArea = !isInContentBounds(x, y);
+                mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
+                mJustPeeked = false;
+                mMotionAborted = false;
+                mPanelClosedOnDown = isFullyCollapsed();
+                mCollapsedAndHeadsUpOnDown = false;
+                mHasLayoutedSinceDown = false;
+                mUpdateFlingOnLayout = false;
+                mTouchAboveFalsingThreshold = false;
+                addMovement(event);
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                final int upPointer = event.getPointerId(event.getActionIndex());
+                if (mTrackingPointer == upPointer) {
+                    // gesture is ongoing, find a new pointer to track
+                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+                    mTrackingPointer = event.getPointerId(newIndex);
+                    mInitialTouchX = event.getX(newIndex);
+                    mInitialTouchY = event.getY(newIndex);
+                }
+                break;
+            case MotionEvent.ACTION_POINTER_DOWN:
+                if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+                    mMotionAborted = true;
+                    mVelocityTracker.clear();
+                }
+                break;
+            case MotionEvent.ACTION_MOVE:
+                final float h = y - mInitialTouchY;
+                addMovement(event);
+                if (scrolledToBottom || mTouchStartedInEmptyArea || mAnimatingOnDown) {
+                    float hAbs = Math.abs(h);
+                    if ((h < -mTouchSlop || (mAnimatingOnDown && hAbs > mTouchSlop))
+                            && hAbs > Math.abs(x - mInitialTouchX)) {
+                        cancelHeightAnimator();
+                        startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
+                        return true;
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                mVelocityTracker.clear();
+                break;
+        }
+        return false;
+    }
+
+    /**
+     * @return Whether a pair of coordinates are inside the visible view content bounds.
+     */
+    protected abstract boolean isInContentBounds(float x, float y);
+
+    protected void cancelHeightAnimator() {
+        if (mHeightAnimator != null) {
+            if (mHeightAnimator.isRunning()) {
+                mPanelUpdateWhenAnimatorEnds = false;
+            }
+            mHeightAnimator.cancel();
+        }
+        endClosing();
+    }
+
+    private void endClosing() {
+        if (mClosing) {
+            mClosing = false;
+            onClosingFinished();
+        }
+    }
+
+    protected boolean isScrolledToBottom() {
+        return true;
+    }
+
+    protected float getContentHeight() {
+        return mExpandedHeight;
     }
 
     @Override
-    public void dispatchConfigurationChanged(Configuration newConfig) {
-        super.dispatchConfigurationChanged(newConfig);
-        mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        loadDimens();
     }
 
-    interface OnConfigurationChangedListener {
-        void onConfigurationChanged(Configuration newConfig);
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        loadDimens();
+    }
+
+    /**
+     * @param vel the current vertical velocity of the motion
+     * @param vectorVel the length of the vectorial velocity
+     * @return whether a fling should expands the panel; contracts otherwise
+     */
+    protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
+        if (mFalsingManager.isUnlockingDisabled()) {
+            return true;
+        }
+
+        if (isFalseTouch(x, y)) {
+            return true;
+        }
+        if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+            return shouldExpandWhenNotFlinging();
+        } else {
+            return vel > 0;
+        }
+    }
+
+    protected boolean shouldExpandWhenNotFlinging() {
+        return getExpandedFraction() > 0.5f;
+    }
+
+    /**
+     * @param x the final x-coordinate when the finger was lifted
+     * @param y the final y-coordinate when the finger was lifted
+     * @return whether this motion should be regarded as a false touch
+     */
+    private boolean isFalseTouch(float x, float y) {
+        if (!mStatusBar.isFalsingThresholdNeeded()) {
+            return false;
+        }
+        if (mFalsingManager.isClassifierEnabled()) {
+            return mFalsingManager.isFalseTouch();
+        }
+        if (!mTouchAboveFalsingThreshold) {
+            return true;
+        }
+        if (mUpwardsWhenTresholdReached) {
+            return false;
+        }
+        return !isDirectionUpwards(x, y);
+    }
+
+    protected void fling(float vel, boolean expand) {
+        fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
+    }
+
+    protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
+        fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
+    }
+
+    protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
+            boolean expandBecauseOfFalsing) {
+        cancelPeek();
+        float target = expand ? getMaxPanelHeight() : 0;
+        if (!expand) {
+            mClosing = true;
+        }
+        flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
+    }
+
+    protected void flingToHeight(float vel, boolean expand, float target,
+            float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
+        // Hack to make the expand transition look nice when clear all button is visible - we make
+        // the animation only to the last notification, and then jump to the maximum panel height so
+        // clear all just fades in and the decelerating motion is towards the last notification.
+        final boolean clearAllExpandHack = expand && fullyExpandedClearAllVisible()
+                && mExpandedHeight < getMaxPanelHeight() - getClearAllHeight()
+                && !isClearAllVisible();
+        if (clearAllExpandHack) {
+            target = getMaxPanelHeight() - getClearAllHeight();
+        }
+        if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
+            notifyExpandingFinished();
+            return;
+        }
+        mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
+        ValueAnimator animator = createHeightAnimator(target);
+        if (expand) {
+            if (expandBecauseOfFalsing && vel < 0) {
+                vel = 0;
+            }
+            mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight());
+            if (vel == 0) {
+                animator.setDuration(350);
+            }
+        } else {
+            if (shouldUseDismissingAnimation()) {
+                if (vel == 0) {
+                    animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+                    long duration = (long) (200 + mExpandedHeight / getHeight() * 100);
+                    animator.setDuration(duration);
+                } else {
+                    mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel,
+                            getHeight());
+                }
+            } else {
+                mFlingAnimationUtilsClosing
+                        .apply(animator, mExpandedHeight, target, vel, getHeight());
+            }
+
+            // Make it shorter if we run a canned animation
+            if (vel == 0) {
+                animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
+            }
+            if (mFixedDuration != NO_FIXED_DURATION) {
+                animator.setDuration(mFixedDuration);
+            }
+        }
+        animator.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCancelled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (clearAllExpandHack && !mCancelled) {
+                    setExpandedHeightInternal(getMaxPanelHeight());
+                }
+                setAnimator(null);
+                if (!mCancelled) {
+                    notifyExpandingFinished();
+                }
+                notifyBarPanelExpansionChanged();
+            }
+        });
+        setAnimator(animator);
+        animator.start();
+    }
+
+    protected abstract boolean shouldUseDismissingAnimation();
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mViewName = getResources().getResourceName(getId());
+    }
+
+    public String getName() {
+        return mViewName;
+    }
+
+    public void setExpandedHeight(float height) {
+        if (DEBUG) logf("setExpandedHeight(%.1f)", height);
+        setExpandedHeightInternal(height + getOverExpansionPixels());
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        mStatusBar.onPanelLaidOut();
+        requestPanelHeightUpdate();
+        mHasLayoutedSinceDown = true;
+        if (mUpdateFlingOnLayout) {
+            abortAnimations();
+            fling(mUpdateFlingVelocity, true /* expands */);
+            mUpdateFlingOnLayout = false;
+        }
+    }
+
+    protected void requestPanelHeightUpdate() {
+        float currentMaxPanelHeight = getMaxPanelHeight();
+
+        if (isFullyCollapsed()) {
+            return;
+        }
+
+        if (currentMaxPanelHeight == mExpandedHeight) {
+            return;
+        }
+
+        if (mPeekAnimator != null || mPeekTouching) {
+            return;
+        }
+
+        if (mTracking && !isTrackingBlocked()) {
+            return;
+        }
+
+        if (mHeightAnimator != null) {
+            mPanelUpdateWhenAnimatorEnds = true;
+            return;
+        }
+
+        setExpandedHeight(currentMaxPanelHeight);
+    }
+
+    public void setExpandedHeightInternal(float h) {
+        if (mExpandLatencyTracking && h != 0f) {
+            DejankUtils.postAfterTraversal(() -> LatencyTracker.getInstance(mContext).onActionEnd(
+                    LatencyTracker.ACTION_EXPAND_PANEL));
+            mExpandLatencyTracking = false;
+        }
+        float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount();
+        if (mHeightAnimator == null) {
+            float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion);
+            if (getOverExpansionPixels() != overExpansionPixels && mTracking) {
+                setOverExpansion(overExpansionPixels, true /* isPixels */);
+            }
+            mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount();
+        } else {
+            mExpandedHeight = h;
+            if (mOverExpandedBeforeFling) {
+                setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */);
+            }
+        }
+
+        // If we are closing the panel and we are almost there due to a slow decelerating
+        // interpolator, abort the animation.
+        if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
+            mExpandedHeight = 0f;
+            if (mHeightAnimator != null) {
+                mHeightAnimator.end();
+            }
+        }
+        mExpandedFraction = Math.min(1f,
+                fhWithoutOverExpansion == 0 ? 0 : mExpandedHeight / fhWithoutOverExpansion);
+        onHeightUpdated(mExpandedHeight);
+        notifyBarPanelExpansionChanged();
+    }
+
+    /**
+     * @return true if the panel tracking should be temporarily blocked; this is used when a
+     *         conflicting gesture (opening QS) is happening
+     */
+    protected abstract boolean isTrackingBlocked();
+
+    protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
+
+    protected abstract void onHeightUpdated(float expandedHeight);
+
+    protected abstract float getOverExpansionAmount();
+
+    protected abstract float getOverExpansionPixels();
+
+    /**
+     * This returns the maximum height of the panel. Children should override this if their
+     * desired height is not the full height.
+     *
+     * @return the default implementation simply returns the maximum height.
+     */
+    protected abstract int getMaxPanelHeight();
+
+    public void setExpandedFraction(float frac) {
+        setExpandedHeight(getMaxPanelHeight() * frac);
+    }
+
+    public float getExpandedHeight() {
+        return mExpandedHeight;
+    }
+
+    public float getExpandedFraction() {
+        return mExpandedFraction;
+    }
+
+    public boolean isFullyExpanded() {
+        return mExpandedHeight >= getMaxPanelHeight();
+    }
+
+    public boolean isFullyCollapsed() {
+        return mExpandedFraction <= 0.0f;
+    }
+
+    public boolean isCollapsing() {
+        return mClosing || mLaunchingNotification;
+    }
+
+    public boolean isTracking() {
+        return mTracking;
+    }
+
+    public void setBar(PanelBar panelBar) {
+        mBar = panelBar;
+    }
+
+    public void collapse(boolean delayed, float speedUpFactor) {
+        if (DEBUG) logf("collapse: " + this);
+        if (canPanelBeCollapsed()) {
+            cancelHeightAnimator();
+            notifyExpandingStarted();
+
+            // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
+            mClosing = true;
+            if (delayed) {
+                mNextCollapseSpeedUpFactor = speedUpFactor;
+                postDelayed(mFlingCollapseRunnable, 120);
+            } else {
+                fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
+            }
+        }
+    }
+
+    public boolean canPanelBeCollapsed() {
+        return !isFullyCollapsed() && !mTracking && !mClosing;
+    }
+
+    private final Runnable mFlingCollapseRunnable = new Runnable() {
+        @Override
+        public void run() {
+            fling(0, false /* expand */, mNextCollapseSpeedUpFactor,
+                    false /* expandBecauseOfFalsing */);
+        }
+    };
+
+    public void cancelPeek() {
+        boolean cancelled = false;
+        if (mPeekAnimator != null) {
+            cancelled = true;
+            mPeekAnimator.cancel();
+        }
+
+        if (cancelled) {
+            // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also
+            // notify mBar that we might have closed ourselves.
+            notifyBarPanelExpansionChanged();
+        }
+    }
+
+    public void expand(final boolean animate) {
+        if (!isFullyCollapsed() && !isCollapsing()) {
+            return;
+        }
+
+        mInstantExpanding = true;
+        mAnimateAfterExpanding = animate;
+        mUpdateFlingOnLayout = false;
+        abortAnimations();
+        cancelPeek();
+        if (mTracking) {
+            onTrackingStopped(true /* expands */); // The panel is expanded after this call.
+        }
+        if (mExpanding) {
+            notifyExpandingFinished();
+        }
+        notifyBarPanelExpansionChanged();
+
+        // Wait for window manager to pickup the change, so we know the maximum height of the panel
+        // then.
+        getViewTreeObserver().addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        if (!mInstantExpanding) {
+                            getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                            return;
+                        }
+                        if (mStatusBar.getStatusBarWindow().getHeight()
+                                != mStatusBar.getStatusBarHeight()) {
+                            getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                            if (mAnimateAfterExpanding) {
+                                notifyExpandingStarted();
+                                fling(0, true /* expand */);
+                            } else {
+                                setExpandedFraction(1f);
+                            }
+                            mInstantExpanding = false;
+                        }
+                    }
+                });
+
+        // Make sure a layout really happens.
+        requestLayout();
+    }
+
+    public void instantCollapse() {
+        abortAnimations();
+        setExpandedFraction(0f);
+        if (mExpanding) {
+            notifyExpandingFinished();
+        }
+        if (mInstantExpanding) {
+            mInstantExpanding = false;
+            notifyBarPanelExpansionChanged();
+        }
+    }
+
+    private void abortAnimations() {
+        cancelPeek();
+        cancelHeightAnimator();
+        removeCallbacks(mPostCollapseRunnable);
+        removeCallbacks(mFlingCollapseRunnable);
+    }
+
+    protected void onClosingFinished() {
+        mBar.onClosingFinished();
+    }
+
+
+    protected void startUnlockHintAnimation() {
+
+        // We don't need to hint the user if an animation is already running or the user is changing
+        // the expansion.
+        if (mHeightAnimator != null || mTracking) {
+            return;
+        }
+        cancelPeek();
+        notifyExpandingStarted();
+        startUnlockHintAnimationPhase1(() -> {
+            notifyExpandingFinished();
+            onUnlockHintFinished();
+            mHintAnimationRunning = false;
+        });
+        onUnlockHintStarted();
+        mHintAnimationRunning = true;
+    }
+
+    protected void onUnlockHintFinished() {
+        mStatusBar.onHintFinished();
+    }
+
+    protected void onUnlockHintStarted() {
+        mStatusBar.onUnlockHintStarted();
+    }
+
+    public boolean isUnlockHintRunning() {
+        return mHintAnimationRunning;
+    }
+
+    /**
+     * Phase 1: Move everything upwards.
+     */
+    private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
+        float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
+        ValueAnimator animator = createHeightAnimator(target);
+        animator.setDuration(250);
+        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        animator.addListener(new AnimatorListenerAdapter() {
+            private boolean mCancelled;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mCancelled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mCancelled) {
+                    setAnimator(null);
+                    onAnimationFinished.run();
+                } else {
+                    startUnlockHintAnimationPhase2(onAnimationFinished);
+                }
+            }
+        });
+        animator.start();
+        setAnimator(animator);
+
+        View[] viewsToAnimate = {
+                mKeyguardBottomArea.getIndicationArea(),
+                mStatusBar.getAmbientIndicationContainer()};
+        for (View v : viewsToAnimate) {
+            if (v == null) {
+                continue;
+            }
+            v.animate()
+                    .translationY(-mHintDistance)
+                    .setDuration(250)
+                    .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+                    .withEndAction(() -> v.animate()
+                            .translationY(0)
+                            .setDuration(450)
+                            .setInterpolator(mBounceInterpolator)
+                            .start())
+                    .start();
+        }
+    }
+
+    private void setAnimator(ValueAnimator animator) {
+        mHeightAnimator = animator;
+        if (animator == null && mPanelUpdateWhenAnimatorEnds) {
+            mPanelUpdateWhenAnimatorEnds = false;
+            requestPanelHeightUpdate();
+        }
+    }
+
+    /**
+     * Phase 2: Bounce down.
+     */
+    private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
+        ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
+        animator.setDuration(450);
+        animator.setInterpolator(mBounceInterpolator);
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                setAnimator(null);
+                onAnimationFinished.run();
+                notifyBarPanelExpansionChanged();
+            }
+        });
+        animator.start();
+        setAnimator(animator);
+    }
+
+    private ValueAnimator createHeightAnimator(float targetHeight) {
+        ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
+        animator.addUpdateListener(
+                animation -> setExpandedHeightInternal((float) animation.getAnimatedValue()));
+        return animator;
+    }
+
+    protected void notifyBarPanelExpansionChanged() {
+        if (mBar != null) {
+            mBar.panelExpansionChanged(mExpandedFraction, mExpandedFraction > 0f
+                    || mPeekAnimator != null || mInstantExpanding
+                    || isPanelVisibleBecauseOfHeadsUp() || mTracking || mHeightAnimator != null);
+        }
+        for (int i = 0; i < mExpansionListeners.size(); i++) {
+            mExpansionListeners.get(i).onPanelExpansionChanged(mExpandedFraction, mTracking);
+        }
+    }
+
+    public void addExpansionListener(PanelExpansionListener panelExpansionListener) {
+        mExpansionListeners.add(panelExpansionListener);
+    }
+
+    protected abstract boolean isPanelVisibleBecauseOfHeadsUp();
+
+    /**
+     * Gets called when the user performs a click anywhere in the empty area of the panel.
+     *
+     * @return whether the panel will be expanded after the action performed by this method
+     */
+    protected boolean onEmptySpaceClick(float x) {
+        if (mHintAnimationRunning) {
+            return true;
+        }
+        return onMiddleClicked();
+    }
+
+    protected final Runnable mPostCollapseRunnable = new Runnable() {
+        @Override
+        public void run() {
+            collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+        }
+    };
+
+    protected abstract boolean onMiddleClicked();
+
+    protected abstract boolean isDozing();
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
+                + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s touchDisabled=%s"
+                + "]",
+                this.getClass().getSimpleName(),
+                getExpandedHeight(),
+                getMaxPanelHeight(),
+                mClosing?"T":"f",
+                mTracking?"T":"f",
+                mJustPeeked?"T":"f",
+                mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
+                mHeightAnimator, ((mHeightAnimator !=null && mHeightAnimator.isStarted())?" (started)":""),
+                mTouchDisabled?"T":"f"
+        ));
+    }
+
+    public abstract void resetViews(boolean animate);
+
+    protected abstract float getPeekHeight();
+    /**
+     * @return whether "Clear all" button will be visible when the panel is fully expanded
+     */
+    protected abstract boolean fullyExpandedClearAllVisible();
+
+    protected abstract boolean isClearAllVisible();
+
+    /**
+     * @return the height of the clear all button, in pixels
+     */
+    protected abstract int getClearAllHeight();
+
+    public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
+        mHeadsUpManager = headsUpManager;
+    }
+
+    public void setLaunchingNotification(boolean launchingNotification) {
+        mLaunchingNotification = launchingNotification;
+    }
+
+    public void collapseWithDuration(int animationDuration) {
+        mFixedDuration = animationDuration;
+        collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+        mFixedDuration = NO_FIXED_DURATION;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
deleted file mode 100644
index 3d8e09a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ /dev/null
@@ -1,1297 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.SystemClock;
-import android.os.VibrationEffect;
-import android.util.Log;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.view.animation.Interpolator;
-
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.LatencyTracker;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.doze.DozeLog;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.FlingAnimationUtils;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-public abstract class PanelViewController {
-    public static final boolean DEBUG = PanelBar.DEBUG;
-    public static final String TAG = PanelView.class.getSimpleName();
-    private static final int INITIAL_OPENING_PEEK_DURATION = 200;
-    private static final int PEEK_ANIMATION_DURATION = 360;
-    private static final int NO_FIXED_DURATION = -1;
-    protected long mDownTime;
-    protected boolean mTouchSlopExceededBeforeDown;
-    private float mMinExpandHeight;
-    private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
-    private boolean mPanelUpdateWhenAnimatorEnds;
-    private boolean mVibrateOnOpening;
-    protected boolean mLaunchingNotification;
-    private int mFixedDuration = NO_FIXED_DURATION;
-    protected ArrayList<PanelExpansionListener> mExpansionListeners = new ArrayList<>();
-
-    private void logf(String fmt, Object... args) {
-        Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
-    }
-
-    protected StatusBar mStatusBar;
-    protected HeadsUpManagerPhone mHeadsUpManager;
-
-    private float mPeekHeight;
-    private float mHintDistance;
-    private float mInitialOffsetOnTouch;
-    private boolean mCollapsedAndHeadsUpOnDown;
-    private float mExpandedFraction = 0;
-    protected float mExpandedHeight = 0;
-    private boolean mPanelClosedOnDown;
-    private boolean mHasLayoutedSinceDown;
-    private float mUpdateFlingVelocity;
-    private boolean mUpdateFlingOnLayout;
-    private boolean mPeekTouching;
-    private boolean mJustPeeked;
-    private boolean mClosing;
-    protected boolean mTracking;
-    private boolean mTouchSlopExceeded;
-    private int mTrackingPointer;
-    protected int mTouchSlop;
-    protected boolean mHintAnimationRunning;
-    private boolean mOverExpandedBeforeFling;
-    private boolean mTouchAboveFalsingThreshold;
-    private int mUnlockFalsingThreshold;
-    private boolean mTouchStartedInEmptyArea;
-    private boolean mMotionAborted;
-    private boolean mUpwardsWhenThresholdReached;
-    private boolean mAnimatingOnDown;
-
-    private ValueAnimator mHeightAnimator;
-    private ObjectAnimator mPeekAnimator;
-    private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
-    private FlingAnimationUtils mFlingAnimationUtils;
-    private FlingAnimationUtils mFlingAnimationUtilsClosing;
-    private FlingAnimationUtils mFlingAnimationUtilsDismissing;
-    private final LatencyTracker mLatencyTracker;
-    private final FalsingManager mFalsingManager;
-    private final DozeLog mDozeLog;
-    private final VibratorHelper mVibratorHelper;
-
-    /**
-     * Whether an instant expand request is currently pending and we are just waiting for layout.
-     */
-    private boolean mInstantExpanding;
-    private boolean mAnimateAfterExpanding;
-
-    PanelBar mBar;
-
-    private String mViewName;
-    private float mInitialTouchY;
-    private float mInitialTouchX;
-    private boolean mTouchDisabled;
-
-    /**
-     * Whether or not the PanelView can be expanded or collapsed with a drag.
-     */
-    private boolean mNotificationsDragEnabled;
-
-    private Interpolator mBounceInterpolator;
-    protected KeyguardBottomAreaView mKeyguardBottomArea;
-
-    /**
-     * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
-     */
-    private float mNextCollapseSpeedUpFactor = 1.0f;
-
-    protected boolean mExpanding;
-    private boolean mGestureWaitForTouchSlop;
-    private boolean mIgnoreXTouchSlop;
-    private boolean mExpandLatencyTracking;
-    private final PanelView mView;
-    protected final Resources mResources;
-    protected final KeyguardStateController mKeyguardStateController;
-    protected final SysuiStatusBarStateController mStatusBarStateController;
-
-    protected void onExpandingFinished() {
-        mBar.onExpandingFinished();
-    }
-
-    protected void onExpandingStarted() {
-    }
-
-    private void notifyExpandingStarted() {
-        if (!mExpanding) {
-            mExpanding = true;
-            onExpandingStarted();
-        }
-    }
-
-    protected final void notifyExpandingFinished() {
-        endClosing();
-        if (mExpanding) {
-            mExpanding = false;
-            onExpandingFinished();
-        }
-    }
-
-    private void runPeekAnimation(long duration, float peekHeight, boolean collapseWhenFinished) {
-        mPeekHeight = peekHeight;
-        if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
-        if (mHeightAnimator != null) {
-            return;
-        }
-        if (mPeekAnimator != null) {
-            mPeekAnimator.cancel();
-        }
-        mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight).setDuration(
-                duration);
-        mPeekAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-        mPeekAnimator.addListener(new AnimatorListenerAdapter() {
-            private boolean mCancelled;
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mCancelled = true;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mPeekAnimator = null;
-                if (!mCancelled && collapseWhenFinished) {
-                    mView.postOnAnimation(mPostCollapseRunnable);
-                }
-
-            }
-        });
-        notifyExpandingStarted();
-        mPeekAnimator.start();
-        mJustPeeked = true;
-    }
-
-    public PanelViewController(PanelView view,
-            FalsingManager falsingManager, DozeLog dozeLog,
-            KeyguardStateController keyguardStateController,
-            SysuiStatusBarStateController statusBarStateController, VibratorHelper vibratorHelper,
-            LatencyTracker latencyTracker, FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
-        mView = view;
-        mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
-            @Override
-            public void onViewAttachedToWindow(View v) {
-                mViewName = mResources.getResourceName(mView.getId());
-            }
-
-            @Override
-            public void onViewDetachedFromWindow(View v) {
-            }
-        });
-
-        mView.addOnLayoutChangeListener(createLayoutChangeListener());
-        mView.setOnTouchListener(createTouchHandler());
-        mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
-
-        mResources = mView.getResources();
-        mKeyguardStateController = keyguardStateController;
-        mStatusBarStateController = statusBarStateController;
-        mFlingAnimationUtils = flingAnimationUtilsBuilder
-                .reset()
-                .setMaxLengthSeconds(0.6f)
-                .setSpeedUpFactor(0.6f)
-                .build();
-        mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder
-                .reset()
-                .setMaxLengthSeconds(0.5f)
-                .setSpeedUpFactor(0.6f)
-                .build();
-        mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder
-                .reset()
-                .setMaxLengthSeconds(0.5f)
-                .setSpeedUpFactor(0.6f)
-                .setX2(0.6f)
-                .setY2(0.84f)
-                .build();
-        mLatencyTracker = latencyTracker;
-        mBounceInterpolator = new BounceInterpolator();
-        mFalsingManager = falsingManager;
-        mDozeLog = dozeLog;
-        mNotificationsDragEnabled = mResources.getBoolean(
-                R.bool.config_enableNotificationShadeDrag);
-        mVibratorHelper = vibratorHelper;
-        mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
-    }
-
-    protected void loadDimens() {
-        final ViewConfiguration configuration = ViewConfiguration.get(mView.getContext());
-        mTouchSlop = configuration.getScaledTouchSlop();
-        mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
-        mUnlockFalsingThreshold = mResources.getDimensionPixelSize(
-                R.dimen.unlock_falsing_threshold);
-    }
-
-    private void addMovement(MotionEvent event) {
-        // Add movement to velocity tracker using raw screen X and Y coordinates instead
-        // of window coordinates because the window frame may be moving at the same time.
-        float deltaX = event.getRawX() - event.getX();
-        float deltaY = event.getRawY() - event.getY();
-        event.offsetLocation(deltaX, deltaY);
-        mVelocityTracker.addMovement(event);
-        event.offsetLocation(-deltaX, -deltaY);
-    }
-
-    public void setTouchAndAnimationDisabled(boolean disabled) {
-        mTouchDisabled = disabled;
-        if (mTouchDisabled) {
-            cancelHeightAnimator();
-            if (mTracking) {
-                onTrackingStopped(true /* expanded */);
-            }
-            notifyExpandingFinished();
-        }
-    }
-
-    public void startExpandLatencyTracking() {
-        if (mLatencyTracker.isEnabled()) {
-            mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL);
-            mExpandLatencyTracking = true;
-        }
-    }
-
-    private void startOpening(MotionEvent event) {
-        runPeekAnimation(INITIAL_OPENING_PEEK_DURATION, getOpeningHeight(),
-                false /* collapseWhenFinished */);
-        notifyBarPanelExpansionChanged();
-        maybeVibrateOnOpening();
-
-        //TODO: keyguard opens QS a different way; log that too?
-
-        // Log the position of the swipe that opened the panel
-        float width = mStatusBar.getDisplayWidth();
-        float height = mStatusBar.getDisplayHeight();
-        int rot = mStatusBar.getRotation();
-
-        mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND,
-                (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot);
-    }
-
-    protected void maybeVibrateOnOpening() {
-        if (mVibrateOnOpening) {
-            mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
-        }
-    }
-
-    protected abstract float getOpeningHeight();
-
-    /**
-     * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
-     * horizontal direction
-     */
-    private boolean isDirectionUpwards(float x, float y) {
-        float xDiff = x - mInitialTouchX;
-        float yDiff = y - mInitialTouchY;
-        if (yDiff >= 0) {
-            return false;
-        }
-        return Math.abs(yDiff) >= Math.abs(xDiff);
-    }
-
-    protected void startExpandingFromPeek() {
-        mStatusBar.handlePeekToExpandTransistion();
-    }
-
-    protected void startExpandMotion(float newX, float newY, boolean startTracking,
-            float expandedHeight) {
-        mInitialOffsetOnTouch = expandedHeight;
-        mInitialTouchY = newY;
-        mInitialTouchX = newX;
-        if (startTracking) {
-            mTouchSlopExceeded = true;
-            setExpandedHeight(mInitialOffsetOnTouch);
-            onTrackingStarted();
-        }
-    }
-
-    private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
-        mTrackingPointer = -1;
-        if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialTouchX) > mTouchSlop
-                || Math.abs(y - mInitialTouchY) > mTouchSlop
-                || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
-            mVelocityTracker.computeCurrentVelocity(1000);
-            float vel = mVelocityTracker.getYVelocity();
-            float vectorVel = (float) Math.hypot(
-                    mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-
-            boolean expand = flingExpands(vel, vectorVel, x, y)
-                    || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel;
-            mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
-                    mStatusBar.isFalsingThresholdNeeded(), mStatusBar.isWakeUpComingFromTouch());
-            // Log collapse gesture if on lock screen.
-            if (!expand && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
-                float displayDensity = mStatusBar.getDisplayDensity();
-                int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity);
-                int velocityDp = (int) Math.abs(vel / displayDensity);
-                mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
-            }
-            fling(vel, expand, isFalseTouch(x, y));
-            onTrackingStopped(expand);
-            mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
-            if (mUpdateFlingOnLayout) {
-                mUpdateFlingVelocity = vel;
-            }
-        } else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking
-                && !mStatusBar.isBouncerShowing()
-                && !mKeyguardStateController.isKeyguardFadingAway()) {
-            long timePassed = SystemClock.uptimeMillis() - mDownTime;
-            if (timePassed < ViewConfiguration.getLongPressTimeout()) {
-                // Lets show the user that he can actually expand the panel
-                runPeekAnimation(
-                        PEEK_ANIMATION_DURATION, getPeekHeight(), true /* collapseWhenFinished */);
-            } else {
-                // We need to collapse the panel since we peeked to the small height.
-                mView.postOnAnimation(mPostCollapseRunnable);
-            }
-        } else if (!mStatusBar.isBouncerShowing()) {
-            boolean expands = onEmptySpaceClick(mInitialTouchX);
-            onTrackingStopped(expands);
-        }
-
-        mVelocityTracker.clear();
-        mPeekTouching = false;
-    }
-
-    protected float getCurrentExpandVelocity() {
-        mVelocityTracker.computeCurrentVelocity(1000);
-        return mVelocityTracker.getYVelocity();
-    }
-
-    private int getFalsingThreshold() {
-        float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
-        return (int) (mUnlockFalsingThreshold * factor);
-    }
-
-    protected abstract boolean shouldGestureWaitForTouchSlop();
-
-    protected abstract boolean shouldGestureIgnoreXTouchSlop(float x, float y);
-
-    protected void onTrackingStopped(boolean expand) {
-        mTracking = false;
-        mBar.onTrackingStopped(expand);
-        notifyBarPanelExpansionChanged();
-    }
-
-    protected void onTrackingStarted() {
-        endClosing();
-        mTracking = true;
-        mBar.onTrackingStarted();
-        notifyExpandingStarted();
-        notifyBarPanelExpansionChanged();
-    }
-
-    /**
-     * @return Whether a pair of coordinates are inside the visible view content bounds.
-     */
-    protected abstract boolean isInContentBounds(float x, float y);
-
-    protected void cancelHeightAnimator() {
-        if (mHeightAnimator != null) {
-            if (mHeightAnimator.isRunning()) {
-                mPanelUpdateWhenAnimatorEnds = false;
-            }
-            mHeightAnimator.cancel();
-        }
-        endClosing();
-    }
-
-    private void endClosing() {
-        if (mClosing) {
-            mClosing = false;
-            onClosingFinished();
-        }
-    }
-
-    protected boolean isScrolledToBottom() {
-        return true;
-    }
-
-    protected float getContentHeight() {
-        return mExpandedHeight;
-    }
-
-    /**
-     * @param vel       the current vertical velocity of the motion
-     * @param vectorVel the length of the vectorial velocity
-     * @return whether a fling should expands the panel; contracts otherwise
-     */
-    protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
-        if (mFalsingManager.isUnlockingDisabled()) {
-            return true;
-        }
-
-        if (isFalseTouch(x, y)) {
-            return true;
-        }
-        if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
-            return shouldExpandWhenNotFlinging();
-        } else {
-            return vel > 0;
-        }
-    }
-
-    protected boolean shouldExpandWhenNotFlinging() {
-        return getExpandedFraction() > 0.5f;
-    }
-
-    /**
-     * @param x the final x-coordinate when the finger was lifted
-     * @param y the final y-coordinate when the finger was lifted
-     * @return whether this motion should be regarded as a false touch
-     */
-    private boolean isFalseTouch(float x, float y) {
-        if (!mStatusBar.isFalsingThresholdNeeded()) {
-            return false;
-        }
-        if (mFalsingManager.isClassifierEnabled()) {
-            return mFalsingManager.isFalseTouch();
-        }
-        if (!mTouchAboveFalsingThreshold) {
-            return true;
-        }
-        if (mUpwardsWhenThresholdReached) {
-            return false;
-        }
-        return !isDirectionUpwards(x, y);
-    }
-
-    protected void fling(float vel, boolean expand) {
-        fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
-    }
-
-    protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
-        fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
-    }
-
-    protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
-            boolean expandBecauseOfFalsing) {
-        cancelPeek();
-        float target = expand ? getMaxPanelHeight() : 0;
-        if (!expand) {
-            mClosing = true;
-        }
-        flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
-    }
-
-    protected void flingToHeight(float vel, boolean expand, float target,
-            float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
-        // Hack to make the expand transition look nice when clear all button is visible - we make
-        // the animation only to the last notification, and then jump to the maximum panel height so
-        // clear all just fades in and the decelerating motion is towards the last notification.
-        final boolean
-                clearAllExpandHack =
-                expand && fullyExpandedClearAllVisible()
-                        && mExpandedHeight < getMaxPanelHeight() - getClearAllHeight()
-                        && !isClearAllVisible();
-        if (clearAllExpandHack) {
-            target = getMaxPanelHeight() - getClearAllHeight();
-        }
-        if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
-            notifyExpandingFinished();
-            return;
-        }
-        mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
-        ValueAnimator animator = createHeightAnimator(target);
-        if (expand) {
-            if (expandBecauseOfFalsing && vel < 0) {
-                vel = 0;
-            }
-            mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, mView.getHeight());
-            if (vel == 0) {
-                animator.setDuration(350);
-            }
-        } else {
-            if (shouldUseDismissingAnimation()) {
-                if (vel == 0) {
-                    animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
-                    long duration = (long) (200 + mExpandedHeight / mView.getHeight() * 100);
-                    animator.setDuration(duration);
-                } else {
-                    mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel,
-                            mView.getHeight());
-                }
-            } else {
-                mFlingAnimationUtilsClosing.apply(
-                        animator, mExpandedHeight, target, vel, mView.getHeight());
-            }
-
-            // Make it shorter if we run a canned animation
-            if (vel == 0) {
-                animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
-            }
-            if (mFixedDuration != NO_FIXED_DURATION) {
-                animator.setDuration(mFixedDuration);
-            }
-        }
-        animator.addListener(new AnimatorListenerAdapter() {
-            private boolean mCancelled;
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mCancelled = true;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (clearAllExpandHack && !mCancelled) {
-                    setExpandedHeightInternal(getMaxPanelHeight());
-                }
-                setAnimator(null);
-                if (!mCancelled) {
-                    notifyExpandingFinished();
-                }
-                notifyBarPanelExpansionChanged();
-            }
-        });
-        setAnimator(animator);
-        animator.start();
-    }
-
-    protected abstract boolean shouldUseDismissingAnimation();
-
-    public String getName() {
-        return mViewName;
-    }
-
-    public void setExpandedHeight(float height) {
-        if (DEBUG) logf("setExpandedHeight(%.1f)", height);
-        setExpandedHeightInternal(height + getOverExpansionPixels());
-    }
-
-    protected void requestPanelHeightUpdate() {
-        float currentMaxPanelHeight = getMaxPanelHeight();
-
-        if (isFullyCollapsed()) {
-            return;
-        }
-
-        if (currentMaxPanelHeight == mExpandedHeight) {
-            return;
-        }
-
-        if (mPeekAnimator != null || mPeekTouching) {
-            return;
-        }
-
-        if (mTracking && !isTrackingBlocked()) {
-            return;
-        }
-
-        if (mHeightAnimator != null) {
-            mPanelUpdateWhenAnimatorEnds = true;
-            return;
-        }
-
-        setExpandedHeight(currentMaxPanelHeight);
-    }
-
-    public void setExpandedHeightInternal(float h) {
-        if (mExpandLatencyTracking && h != 0f) {
-            DejankUtils.postAfterTraversal(
-                    () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
-            mExpandLatencyTracking = false;
-        }
-        float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount();
-        if (mHeightAnimator == null) {
-            float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion);
-            if (getOverExpansionPixels() != overExpansionPixels && mTracking) {
-                setOverExpansion(overExpansionPixels, true /* isPixels */);
-            }
-            mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount();
-        } else {
-            mExpandedHeight = h;
-            if (mOverExpandedBeforeFling) {
-                setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */);
-            }
-        }
-
-        // If we are closing the panel and we are almost there due to a slow decelerating
-        // interpolator, abort the animation.
-        if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
-            mExpandedHeight = 0f;
-            if (mHeightAnimator != null) {
-                mHeightAnimator.end();
-            }
-        }
-        mExpandedFraction = Math.min(1f,
-                fhWithoutOverExpansion == 0 ? 0 : mExpandedHeight / fhWithoutOverExpansion);
-        onHeightUpdated(mExpandedHeight);
-        notifyBarPanelExpansionChanged();
-    }
-
-    /**
-     * @return true if the panel tracking should be temporarily blocked; this is used when a
-     * conflicting gesture (opening QS) is happening
-     */
-    protected abstract boolean isTrackingBlocked();
-
-    protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
-
-    protected abstract void onHeightUpdated(float expandedHeight);
-
-    protected abstract float getOverExpansionAmount();
-
-    protected abstract float getOverExpansionPixels();
-
-    /**
-     * This returns the maximum height of the panel. Children should override this if their
-     * desired height is not the full height.
-     *
-     * @return the default implementation simply returns the maximum height.
-     */
-    protected abstract int getMaxPanelHeight();
-
-    public void setExpandedFraction(float frac) {
-        setExpandedHeight(getMaxPanelHeight() * frac);
-    }
-
-    public float getExpandedHeight() {
-        return mExpandedHeight;
-    }
-
-    public float getExpandedFraction() {
-        return mExpandedFraction;
-    }
-
-    public boolean isFullyExpanded() {
-        return mExpandedHeight >= getMaxPanelHeight();
-    }
-
-    public boolean isFullyCollapsed() {
-        return mExpandedFraction <= 0.0f;
-    }
-
-    public boolean isCollapsing() {
-        return mClosing || mLaunchingNotification;
-    }
-
-    public boolean isTracking() {
-        return mTracking;
-    }
-
-    public void setBar(PanelBar panelBar) {
-        mBar = panelBar;
-    }
-
-    public void collapse(boolean delayed, float speedUpFactor) {
-        if (DEBUG) logf("collapse: " + this);
-        if (canPanelBeCollapsed()) {
-            cancelHeightAnimator();
-            notifyExpandingStarted();
-
-            // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
-            mClosing = true;
-            if (delayed) {
-                mNextCollapseSpeedUpFactor = speedUpFactor;
-                mView.postDelayed(mFlingCollapseRunnable, 120);
-            } else {
-                fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
-            }
-        }
-    }
-
-    public boolean canPanelBeCollapsed() {
-        return !isFullyCollapsed() && !mTracking && !mClosing;
-    }
-
-    private final Runnable mFlingCollapseRunnable = new Runnable() {
-        @Override
-        public void run() {
-            fling(0, false /* expand */, mNextCollapseSpeedUpFactor,
-                    false /* expandBecauseOfFalsing */);
-        }
-    };
-
-    public void cancelPeek() {
-        boolean cancelled = false;
-        if (mPeekAnimator != null) {
-            cancelled = true;
-            mPeekAnimator.cancel();
-        }
-
-        if (cancelled) {
-            // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also
-            // notify mBar that we might have closed ourselves.
-            notifyBarPanelExpansionChanged();
-        }
-    }
-
-    public void expand(final boolean animate) {
-        if (!isFullyCollapsed() && !isCollapsing()) {
-            return;
-        }
-
-        mInstantExpanding = true;
-        mAnimateAfterExpanding = animate;
-        mUpdateFlingOnLayout = false;
-        abortAnimations();
-        cancelPeek();
-        if (mTracking) {
-            onTrackingStopped(true /* expands */); // The panel is expanded after this call.
-        }
-        if (mExpanding) {
-            notifyExpandingFinished();
-        }
-        notifyBarPanelExpansionChanged();
-
-        // Wait for window manager to pickup the change, so we know the maximum height of the panel
-        // then.
-        mView.getViewTreeObserver().addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        if (!mInstantExpanding) {
-                            mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                            return;
-                        }
-                        if (mStatusBar.getStatusBarWindow().getHeight()
-                                != mStatusBar.getStatusBarHeight()) {
-                            mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                            if (mAnimateAfterExpanding) {
-                                notifyExpandingStarted();
-                                fling(0, true /* expand */);
-                            } else {
-                                setExpandedFraction(1f);
-                            }
-                            mInstantExpanding = false;
-                        }
-                    }
-                });
-
-        // Make sure a layout really happens.
-        mView.requestLayout();
-    }
-
-    public void instantCollapse() {
-        abortAnimations();
-        setExpandedFraction(0f);
-        if (mExpanding) {
-            notifyExpandingFinished();
-        }
-        if (mInstantExpanding) {
-            mInstantExpanding = false;
-            notifyBarPanelExpansionChanged();
-        }
-    }
-
-    private void abortAnimations() {
-        cancelPeek();
-        cancelHeightAnimator();
-        mView.removeCallbacks(mPostCollapseRunnable);
-        mView.removeCallbacks(mFlingCollapseRunnable);
-    }
-
-    protected void onClosingFinished() {
-        mBar.onClosingFinished();
-    }
-
-
-    protected void startUnlockHintAnimation() {
-
-        // We don't need to hint the user if an animation is already running or the user is changing
-        // the expansion.
-        if (mHeightAnimator != null || mTracking) {
-            return;
-        }
-        cancelPeek();
-        notifyExpandingStarted();
-        startUnlockHintAnimationPhase1(() -> {
-            notifyExpandingFinished();
-            onUnlockHintFinished();
-            mHintAnimationRunning = false;
-        });
-        onUnlockHintStarted();
-        mHintAnimationRunning = true;
-    }
-
-    protected void onUnlockHintFinished() {
-        mStatusBar.onHintFinished();
-    }
-
-    protected void onUnlockHintStarted() {
-        mStatusBar.onUnlockHintStarted();
-    }
-
-    public boolean isUnlockHintRunning() {
-        return mHintAnimationRunning;
-    }
-
-    /**
-     * Phase 1: Move everything upwards.
-     */
-    private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
-        float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
-        ValueAnimator animator = createHeightAnimator(target);
-        animator.setDuration(250);
-        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        animator.addListener(new AnimatorListenerAdapter() {
-            private boolean mCancelled;
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mCancelled = true;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (mCancelled) {
-                    setAnimator(null);
-                    onAnimationFinished.run();
-                } else {
-                    startUnlockHintAnimationPhase2(onAnimationFinished);
-                }
-            }
-        });
-        animator.start();
-        setAnimator(animator);
-
-        View[] viewsToAnimate = {
-                mKeyguardBottomArea.getIndicationArea(),
-                mStatusBar.getAmbientIndicationContainer()};
-        for (View v : viewsToAnimate) {
-            if (v == null) {
-                continue;
-            }
-            v.animate().translationY(-mHintDistance).setDuration(250).setInterpolator(
-                    Interpolators.FAST_OUT_SLOW_IN).withEndAction(() -> v.animate().translationY(
-                    0).setDuration(450).setInterpolator(mBounceInterpolator).start()).start();
-        }
-    }
-
-    private void setAnimator(ValueAnimator animator) {
-        mHeightAnimator = animator;
-        if (animator == null && mPanelUpdateWhenAnimatorEnds) {
-            mPanelUpdateWhenAnimatorEnds = false;
-            requestPanelHeightUpdate();
-        }
-    }
-
-    /**
-     * Phase 2: Bounce down.
-     */
-    private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
-        ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
-        animator.setDuration(450);
-        animator.setInterpolator(mBounceInterpolator);
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                setAnimator(null);
-                onAnimationFinished.run();
-                notifyBarPanelExpansionChanged();
-            }
-        });
-        animator.start();
-        setAnimator(animator);
-    }
-
-    private ValueAnimator createHeightAnimator(float targetHeight) {
-        ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
-        animator.addUpdateListener(
-                animation -> setExpandedHeightInternal((float) animation.getAnimatedValue()));
-        return animator;
-    }
-
-    protected void notifyBarPanelExpansionChanged() {
-        if (mBar != null) {
-            mBar.panelExpansionChanged(
-                    mExpandedFraction,
-                    mExpandedFraction > 0f || mPeekAnimator != null || mInstantExpanding
-                            || isPanelVisibleBecauseOfHeadsUp() || mTracking
-                            || mHeightAnimator != null);
-        }
-        for (int i = 0; i < mExpansionListeners.size(); i++) {
-            mExpansionListeners.get(i).onPanelExpansionChanged(mExpandedFraction, mTracking);
-        }
-    }
-
-    public void addExpansionListener(PanelExpansionListener panelExpansionListener) {
-        mExpansionListeners.add(panelExpansionListener);
-    }
-
-    protected abstract boolean isPanelVisibleBecauseOfHeadsUp();
-
-    /**
-     * Gets called when the user performs a click anywhere in the empty area of the panel.
-     *
-     * @return whether the panel will be expanded after the action performed by this method
-     */
-    protected boolean onEmptySpaceClick(float x) {
-        if (mHintAnimationRunning) {
-            return true;
-        }
-        return onMiddleClicked();
-    }
-
-    protected final Runnable mPostCollapseRunnable = new Runnable() {
-        @Override
-        public void run() {
-            collapse(false /* delayed */, 1.0f /* speedUpFactor */);
-        }
-    };
-
-    protected abstract boolean onMiddleClicked();
-
-    protected abstract boolean isDozing();
-
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
-                        + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s "
-                        + "touchDisabled=%s" + "]",
-                this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(),
-                mClosing ? "T" : "f", mTracking ? "T" : "f", mJustPeeked ? "T" : "f", mPeekAnimator,
-                ((mPeekAnimator != null && mPeekAnimator.isStarted()) ? " (started)" : ""),
-                mHeightAnimator,
-                ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""),
-                mTouchDisabled ? "T" : "f"));
-    }
-
-    public abstract void resetViews(boolean animate);
-
-    protected abstract float getPeekHeight();
-
-    /**
-     * @return whether "Clear all" button will be visible when the panel is fully expanded
-     */
-    protected abstract boolean fullyExpandedClearAllVisible();
-
-    protected abstract boolean isClearAllVisible();
-
-    /**
-     * @return the height of the clear all button, in pixels
-     */
-    protected abstract int getClearAllHeight();
-
-    public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
-        mHeadsUpManager = headsUpManager;
-    }
-
-    public void setLaunchingNotification(boolean launchingNotification) {
-        mLaunchingNotification = launchingNotification;
-    }
-
-    public void collapseWithDuration(int animationDuration) {
-        mFixedDuration = animationDuration;
-        collapse(false /* delayed */, 1.0f /* speedUpFactor */);
-        mFixedDuration = NO_FIXED_DURATION;
-    }
-
-    public ViewGroup getView() {
-        // TODO: remove this method, or at least reduce references to it.
-        return mView;
-    }
-
-    public boolean isEnabled() {
-        return mView.isEnabled();
-    }
-
-    public OnLayoutChangeListener createLayoutChangeListener() {
-        return new OnLayoutChangeListener();
-    }
-
-    protected TouchHandler createTouchHandler() {
-        return new TouchHandler();
-    }
-
-    protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
-        return new OnConfigurationChangedListener();
-    }
-
-    public class TouchHandler implements View.OnTouchListener {
-        public boolean onInterceptTouchEvent(MotionEvent event) {
-            if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
-                    && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
-                return false;
-            }
-
-            /*
-             * If the user drags anywhere inside the panel we intercept it if the movement is
-             * upwards. This allows closing the shade from anywhere inside the panel.
-             *
-             * We only do this if the current content is scrolled to the bottom,
-             * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling
-             * gesture
-             * possible.
-             */
-            int pointerIndex = event.findPointerIndex(mTrackingPointer);
-            if (pointerIndex < 0) {
-                pointerIndex = 0;
-                mTrackingPointer = event.getPointerId(pointerIndex);
-            }
-            final float x = event.getX(pointerIndex);
-            final float y = event.getY(pointerIndex);
-            boolean scrolledToBottom = isScrolledToBottom();
-
-            switch (event.getActionMasked()) {
-                case MotionEvent.ACTION_DOWN:
-                    mStatusBar.userActivity();
-                    mAnimatingOnDown = mHeightAnimator != null;
-                    mMinExpandHeight = 0.0f;
-                    mDownTime = SystemClock.uptimeMillis();
-                    if (mAnimatingOnDown && mClosing && !mHintAnimationRunning
-                            || mPeekAnimator != null) {
-                        cancelHeightAnimator();
-                        cancelPeek();
-                        mTouchSlopExceeded = true;
-                        return true;
-                    }
-                    mInitialTouchY = y;
-                    mInitialTouchX = x;
-                    mTouchStartedInEmptyArea = !isInContentBounds(x, y);
-                    mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
-                    mJustPeeked = false;
-                    mMotionAborted = false;
-                    mPanelClosedOnDown = isFullyCollapsed();
-                    mCollapsedAndHeadsUpOnDown = false;
-                    mHasLayoutedSinceDown = false;
-                    mUpdateFlingOnLayout = false;
-                    mTouchAboveFalsingThreshold = false;
-                    addMovement(event);
-                    break;
-                case MotionEvent.ACTION_POINTER_UP:
-                    final int upPointer = event.getPointerId(event.getActionIndex());
-                    if (mTrackingPointer == upPointer) {
-                        // gesture is ongoing, find a new pointer to track
-                        final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
-                        mTrackingPointer = event.getPointerId(newIndex);
-                        mInitialTouchX = event.getX(newIndex);
-                        mInitialTouchY = event.getY(newIndex);
-                    }
-                    break;
-                case MotionEvent.ACTION_POINTER_DOWN:
-                    if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
-                        mMotionAborted = true;
-                        mVelocityTracker.clear();
-                    }
-                    break;
-                case MotionEvent.ACTION_MOVE:
-                    final float h = y - mInitialTouchY;
-                    addMovement(event);
-                    if (scrolledToBottom || mTouchStartedInEmptyArea || mAnimatingOnDown) {
-                        float hAbs = Math.abs(h);
-                        if ((h < -mTouchSlop || (mAnimatingOnDown && hAbs > mTouchSlop))
-                                && hAbs > Math.abs(x - mInitialTouchX)) {
-                            cancelHeightAnimator();
-                            startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
-                            return true;
-                        }
-                    }
-                    break;
-                case MotionEvent.ACTION_CANCEL:
-                case MotionEvent.ACTION_UP:
-                    mVelocityTracker.clear();
-                    break;
-            }
-            return false;
-        }
-
-        @Override
-        public boolean onTouch(View v, MotionEvent event) {
-            if (mInstantExpanding || (mTouchDisabled
-                    && event.getActionMasked() != MotionEvent.ACTION_CANCEL) || (mMotionAborted
-                    && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
-                return false;
-            }
-
-            // If dragging should not expand the notifications shade, then return false.
-            if (!mNotificationsDragEnabled) {
-                if (mTracking) {
-                    // Turn off tracking if it's on or the shade can get stuck in the down position.
-                    onTrackingStopped(true /* expand */);
-                }
-                return false;
-            }
-
-            // On expanding, single mouse click expands the panel instead of dragging.
-            if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
-                if (event.getAction() == MotionEvent.ACTION_UP) {
-                    expand(true);
-                }
-                return true;
-            }
-
-            /*
-             * We capture touch events here and update the expand height here in case according to
-             * the users fingers. This also handles multi-touch.
-             *
-             * If the user just clicks shortly, we show a quick peek of the shade.
-             *
-             * Flinging is also enabled in order to open or close the shade.
-             */
-
-            int pointerIndex = event.findPointerIndex(mTrackingPointer);
-            if (pointerIndex < 0) {
-                pointerIndex = 0;
-                mTrackingPointer = event.getPointerId(pointerIndex);
-            }
-            final float x = event.getX(pointerIndex);
-            final float y = event.getY(pointerIndex);
-
-            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
-                mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y);
-            }
-
-            switch (event.getActionMasked()) {
-                case MotionEvent.ACTION_DOWN:
-                    startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
-                    mJustPeeked = false;
-                    mMinExpandHeight = 0.0f;
-                    mPanelClosedOnDown = isFullyCollapsed();
-                    mHasLayoutedSinceDown = false;
-                    mUpdateFlingOnLayout = false;
-                    mMotionAborted = false;
-                    mPeekTouching = mPanelClosedOnDown;
-                    mDownTime = SystemClock.uptimeMillis();
-                    mTouchAboveFalsingThreshold = false;
-                    mCollapsedAndHeadsUpOnDown =
-                            isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp();
-                    addMovement(event);
-                    if (!mGestureWaitForTouchSlop || (mHeightAnimator != null
-                            && !mHintAnimationRunning) || mPeekAnimator != null) {
-                        mTouchSlopExceeded =
-                                (mHeightAnimator != null && !mHintAnimationRunning)
-                                        || mPeekAnimator != null || mTouchSlopExceededBeforeDown;
-                        cancelHeightAnimator();
-                        cancelPeek();
-                        onTrackingStarted();
-                    }
-                    if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()
-                            && !mStatusBar.isBouncerShowing()) {
-                        startOpening(event);
-                    }
-                    break;
-
-                case MotionEvent.ACTION_POINTER_UP:
-                    final int upPointer = event.getPointerId(event.getActionIndex());
-                    if (mTrackingPointer == upPointer) {
-                        // gesture is ongoing, find a new pointer to track
-                        final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
-                        final float newY = event.getY(newIndex);
-                        final float newX = event.getX(newIndex);
-                        mTrackingPointer = event.getPointerId(newIndex);
-                        startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
-                    }
-                    break;
-                case MotionEvent.ACTION_POINTER_DOWN:
-                    if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
-                        mMotionAborted = true;
-                        endMotionEvent(event, x, y, true /* forceCancel */);
-                        return false;
-                    }
-                    break;
-                case MotionEvent.ACTION_MOVE:
-                    addMovement(event);
-                    float h = y - mInitialTouchY;
-
-                    // If the panel was collapsed when touching, we only need to check for the
-                    // y-component of the gesture, as we have no conflicting horizontal gesture.
-                    if (Math.abs(h) > mTouchSlop && (Math.abs(h) > Math.abs(x - mInitialTouchX)
-                            || mIgnoreXTouchSlop)) {
-                        mTouchSlopExceeded = true;
-                        if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
-                            if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
-                                startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
-                                h = 0;
-                            }
-                            cancelHeightAnimator();
-                            onTrackingStarted();
-                        }
-                    }
-                    float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
-                    if (newHeight > mPeekHeight) {
-                        if (mPeekAnimator != null) {
-                            mPeekAnimator.cancel();
-                        }
-                        mJustPeeked = false;
-                    } else if (mPeekAnimator == null && mJustPeeked) {
-                        // The initial peek has finished, but we haven't dragged as far yet, lets
-                        // speed it up by starting at the peek height.
-                        mInitialOffsetOnTouch = mExpandedHeight;
-                        mInitialTouchY = y;
-                        mMinExpandHeight = mExpandedHeight;
-                        mJustPeeked = false;
-                    }
-                    newHeight = Math.max(newHeight, mMinExpandHeight);
-                    if (-h >= getFalsingThreshold()) {
-                        mTouchAboveFalsingThreshold = true;
-                        mUpwardsWhenThresholdReached = isDirectionUpwards(x, y);
-                    }
-                    if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking)
-                            && !isTrackingBlocked()) {
-                        setExpandedHeightInternal(newHeight);
-                    }
-                    break;
-
-                case MotionEvent.ACTION_UP:
-                case MotionEvent.ACTION_CANCEL:
-                    addMovement(event);
-                    endMotionEvent(event, x, y, false /* forceCancel */);
-                    break;
-            }
-            return !mGestureWaitForTouchSlop || mTracking;
-        }
-    }
-
-    public class OnLayoutChangeListener implements View.OnLayoutChangeListener {
-        @Override
-        public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
-                int oldTop, int oldRight, int oldBottom) {
-            mStatusBar.onPanelLaidOut();
-            requestPanelHeightUpdate();
-            mHasLayoutedSinceDown = true;
-            if (mUpdateFlingOnLayout) {
-                abortAnimations();
-                fling(mUpdateFlingVelocity, true /* expands */);
-                mUpdateFlingOnLayout = false;
-            }
-        }
-    }
-
-    public class OnConfigurationChangedListener implements
-            PanelView.OnConfigurationChangedListener {
-        @Override
-        public void onConfigurationChanged(Configuration newConfig) {
-            loadDimens();
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 45f3bf9..312ca26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -236,7 +236,7 @@
     public void onPanelFullyOpened() {
         super.onPanelFullyOpened();
         if (!mIsFullyOpenedPanel) {
-            mPanel.getView().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+            mPanel.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
         }
         mIsFullyOpenedPanel = true;
         maybeShowDivider(!mBar.mPanelExpanded);
@@ -420,8 +420,7 @@
 
     void maybeShowDivider(boolean showDivider) {
         int state =
-                showDivider && NotificationPanelViewController.isQsSplitEnabled()
-                        ? View.VISIBLE : View.GONE;
+                showDivider && NotificationPanelView.isQsSplitEnabled() ? View.VISIBLE : View.GONE;
         mDividerContainer.setVisibility(state);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index 866dc2d..57e7014 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -79,7 +79,7 @@
     public void instantExpandNotificationsPanel() {
         // Make our window larger and the panel expanded.
         getStatusBar().makeExpandedVisible(true /* force */);
-        getNotificationPanelViewController().expand(false /* animate */);
+        getNotificationPanelView().expand(false /* animate */);
         mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
     }
 
@@ -123,9 +123,8 @@
 
         // TODO(b/62444020): remove when this bug is fixed
         Log.v(TAG, "mStatusBarWindow: " + getStatusBarWindowView() + " canPanelBeCollapsed(): "
-                + getNotificationPanelViewController().canPanelBeCollapsed());
-        if (getStatusBarWindowView() != null
-                && getNotificationPanelViewController().canPanelBeCollapsed()) {
+                + getNotificationPanelView().canPanelBeCollapsed());
+        if (getStatusBarWindowView() != null && getNotificationPanelView().canPanelBeCollapsed()) {
             // release focus immediately to kick off focus change transition
             mStatusBarWindowController.setStatusBarFocusable(false);
 
@@ -139,7 +138,7 @@
 
     @Override
     public boolean closeShadeIfOpen() {
-        if (!getNotificationPanelViewController().isFullyCollapsed()) {
+        if (!getNotificationPanelView().isFullyCollapsed()) {
             mCommandQueue.animateCollapsePanels(
                     CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
             getStatusBar().visibilityChanged(false);
@@ -150,14 +149,15 @@
 
     @Override
     public void postOnShadeExpanded(Runnable executable) {
-        getNotificationPanelViewController().addOnGlobalLayoutListener(
+        getNotificationPanelView().getViewTreeObserver().addOnGlobalLayoutListener(
                 new ViewTreeObserver.OnGlobalLayoutListener() {
                     @Override
                     public void onGlobalLayout() {
                         if (getStatusBar().getStatusBarWindow().getHeight()
                                 != getStatusBar().getStatusBarHeight()) {
-                            getNotificationPanelViewController().removeOnGlobalLayoutListener(this);
-                            getNotificationPanelViewController().getView().post(executable);
+                            getNotificationPanelView().getViewTreeObserver()
+                                    .removeOnGlobalLayoutListener(this);
+                            getNotificationPanelView().post(executable);
                         }
                     }
                 });
@@ -187,7 +187,7 @@
 
     @Override
     public boolean collapsePanel() {
-        if (!getNotificationPanelViewController().isFullyCollapsed()) {
+        if (!getNotificationPanelView().isFullyCollapsed()) {
             // close the shade if it was open
             animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
                     true /* force */, true /* delayed */);
@@ -230,7 +230,7 @@
         return (PhoneStatusBarView) getStatusBar().getStatusBarView();
     }
 
-    private NotificationPanelViewController getNotificationPanelViewController() {
-        return getStatusBar().getPanelController();
+    private NotificationPanelView getNotificationPanelView() {
+        return getStatusBar().getPanel();
     }
 }
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 277a761..c5d7da4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -131,6 +131,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.EventLogTags;
 import com.android.systemui.InitController;
+import com.android.systemui.Interpolators;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
@@ -198,6 +199,7 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.NotificationListController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
@@ -206,7 +208,6 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -391,8 +392,7 @@
     private final DismissCallbackRegistry mDismissCallbackRegistry;
 
     // expanded notifications
-    // the sliding/resizing panel within the notification window
-    protected NotificationPanelViewController mNotificationPanelViewController;
+    protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
 
     // settings
     private QSPanel mQSPanel;
@@ -461,8 +461,8 @@
                 mUserSetup = userSetup;
                 if (!mUserSetup && mStatusBarView != null)
                     animateCollapseQuickSettings();
-                if (mNotificationPanelViewController != null) {
-                    mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
+                if (mNotificationPanel != null) {
+                    mNotificationPanel.setUserSetupComplete(mUserSetup);
                 }
                 updateQsExpansionEnabled();
             }
@@ -913,8 +913,9 @@
 
         mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
         mDozeServiceHost.initialize(this, mNotificationIconAreaController,
-                mStatusBarKeyguardViewManager, mStatusBarWindowViewController,
-                mNotificationPanelViewController, mAmbientIndicationContainer);
+                mStatusBarKeyguardViewManager,
+                mStatusBarWindowViewController,
+                mNotificationPanel, mAmbientIndicationContainer);
 
         mConfigurationController.addCallback(this);
 
@@ -984,6 +985,8 @@
 
         // TODO: Deal with the ugliness that comes from having some of the statusbar broken out
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
+        mNotificationPanel = mSuperStatusBarViewFactory.getNotificationPanelView();
+
         mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
         NotificationListContainer notifListContainer = (NotificationListContainer) mStackScroller;
         mNotificationLogger.setUpWithContainer(notifListContainer);
@@ -997,9 +1000,8 @@
         mWakeUpCoordinator.setIconAreaController(mNotificationIconAreaController);
         inflateShelf();
         mNotificationIconAreaController.setupShelf(mNotificationShelf);
-        mNotificationPanelViewController.setOnReinflationListener(
-                mNotificationIconAreaController::initAodIcons);
-        mNotificationPanelViewController.addExpansionListener(mWakeUpCoordinator);
+        mNotificationPanel.setOnReinflationListener(mNotificationIconAreaController::initAodIcons);
+        mNotificationPanel.addExpansionListener(mWakeUpCoordinator);
 
         mDarkIconDispatcher.addDarkReceiver(mNotificationIconAreaController);
         // Allow plugins to reference DarkIconDispatcher and StatusBarStateController
@@ -1013,7 +1015,7 @@
                     PhoneStatusBarView oldStatusBarView = mStatusBarView;
                     mStatusBarView = (PhoneStatusBarView) fragment.getView();
                     mStatusBarView.setBar(this);
-                    mStatusBarView.setPanel(mNotificationPanelViewController);
+                    mStatusBarView.setPanel(mNotificationPanel);
                     mStatusBarView.setScrimController(mScrimController);
 
                     // CollapsedStatusBarFragment re-inflated PhoneStatusBarView and both of
@@ -1024,7 +1026,7 @@
                     // it needs to notify PhoneStatusBarView's new instance to update the correct
                     // status by calling mNotificationPanel.notifyBarPanelExpansionChanged().
                     if (mHeadsUpManager.hasPinnedHeadsUp()) {
-                        mNotificationPanelViewController.notifyBarPanelExpansionChanged();
+                        mNotificationPanel.notifyBarPanelExpansionChanged();
                     }
                     mStatusBarView.setBouncerShowing(mBouncerShowing);
                     if (oldStatusBarView != null) {
@@ -1038,12 +1040,10 @@
                         // This view is being recreated, let's destroy the old one
                         mHeadsUpAppearanceController.destroy();
                     }
-                    // TODO: this should probably be scoped to the StatusBarComponent
                     mHeadsUpAppearanceController = new HeadsUpAppearanceController(
                             mNotificationIconAreaController, mHeadsUpManager, mStatusBarWindow,
                             mStatusBarStateController, mKeyguardBypassController,
-                            mKeyguardStateController, mWakeUpCoordinator, mCommandQueue,
-                            mNotificationPanelViewController);
+                            mKeyguardStateController, mWakeUpCoordinator, mCommandQueue);
                     mHeadsUpAppearanceController.readFrom(oldController);
 
                     mLightsOutNotifController.setLightsOutNotifView(
@@ -1059,11 +1059,11 @@
         mHeadsUpManager.setUp(mStatusBarWindow, mGroupManager, this, mVisualStabilityManager);
         mConfigurationController.addCallback(mHeadsUpManager);
         mHeadsUpManager.addListener(this);
-        mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener());
+        mHeadsUpManager.addListener(mNotificationPanel);
         mHeadsUpManager.addListener(mGroupManager);
         mHeadsUpManager.addListener(mGroupAlertTransferHelper);
         mHeadsUpManager.addListener(mVisualStabilityManager);
-        mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
+        mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
         mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
         mNotificationLogger.setHeadsUpManager(mHeadsUpManager);
@@ -1078,8 +1078,7 @@
                 SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
                         mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
                         mStatusBarWindow.findViewById(R.id.lock_icon));
-        mNotificationPanelViewController.setKeyguardIndicationController(
-                mKeyguardIndicationController);
+        mNotificationPanel.setKeyguardIndicationController(mKeyguardIndicationController);
 
         mAmbientIndicationContainer = mStatusBarWindow.findViewById(
                 R.id.ambient_indication_container);
@@ -1114,19 +1113,19 @@
         });
         mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble);
 
-        mNotificationPanelViewController.initDependencies(this, mGroupManager, mNotificationShelf,
-                mNotificationIconAreaController, mScrimController);
+        mNotificationPanel.initDependencies(this, mGroupManager, mNotificationShelf,
+                mHeadsUpManager, mNotificationIconAreaController, mScrimController);
 
         BackDropView backdrop = mStatusBarWindow.findViewById(R.id.backdrop);
         mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
                 backdrop.findViewById(R.id.backdrop_back), mScrimController, mLockscreenWallpaper);
 
-        mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
+        mNotificationPanel.setUserSetupComplete(mUserSetup);
         if (UserManager.get(mContext).isUserSwitcherEnabled()) {
             createUserSwitcher();
         }
 
-        mNotificationPanelViewController.setLaunchAffordanceListener(
+        mNotificationPanel.setLaunchAffordanceListener(
                 mLockscreenLockIconController::onShowingLaunchAffordanceChanged);
 
         // Set up the quick settings tile panel
@@ -1140,7 +1139,6 @@
                             .withDefault(this::createDefaultQSFragment)
                             .build());
             mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
-                    mNotificationPanelViewController,
                     (visible) -> {
                         mBrightnessMirrorVisible = visible;
                         updateScrimController();
@@ -1234,7 +1232,7 @@
     private void setUpPresenter() {
         // Set up the initial notification state.
         mActivityLaunchAnimator = new ActivityLaunchAnimator(
-                mStatusBarWindowViewController, this, mNotificationPanelViewController,
+                mStatusBarWindowViewController, this, mNotificationPanel,
                 (NotificationListContainer) mStackScroller);
 
         final NotificationRowBinderImpl rowBinder =
@@ -1246,7 +1244,7 @@
                         mNotificationLogger);
 
         // TODO: inject this.
-        mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
+        mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanel,
                 mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController,
                 mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
                 mNotificationAlertingManager, rowBinder, mKeyguardStateController,
@@ -1267,12 +1265,15 @@
                         .setStatusBar(this)
                         .setActivityLaunchAnimator(mActivityLaunchAnimator)
                         .setNotificationPresenter(mPresenter)
-                        .setNotificationPanelViewController(mNotificationPanelViewController)
                         .build();
 
         mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
 
-        mEntryManager.setRowBinder(rowBinder);
+        if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+            mEntryManager.setRowBinder(rowBinder);
+            rowBinder.setInflationCallback(mEntryManager);
+        }
+
         mRemoteInputUriController.attach(mEntryManager);
 
         rowBinder.setNotificationClicker(new NotificationClicker(
@@ -1282,7 +1283,7 @@
         mNotificationListController.bind();
 
         if (mFeatureFlags.isNewNotifPipelineEnabled()) {
-            mNewNotifPipeline.get().initialize(mNotificationListener);
+            mNewNotifPipeline.get().initialize(mNotificationListener, rowBinder);
         }
         mEntryManager.attach(mNotificationListener);
     }
@@ -1377,7 +1378,7 @@
         }
         // We need the new R.id.keyguard_indication_area before recreating
         // mKeyguardIndicationController
-        mNotificationPanelViewController.onThemeChanged();
+        mNotificationPanel.onThemeChanged();
         onThemeChanged();
     }
 
@@ -1392,7 +1393,7 @@
         mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
                 mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
                 mStatusBarWindow.findViewById(R.id.keyguard_header),
-                mNotificationPanelViewController);
+                mNotificationPanel);
     }
 
     private void inflateStatusBarWindow() {
@@ -1401,17 +1402,16 @@
                 .statusBarWindowView(mStatusBarWindow).build();
         mStatusBarWindowViewController = statusBarComponent.getStatusBarWindowViewController();
         mStatusBarWindowViewController.setupExpandedStatusBar();
-        mNotificationPanelViewController = statusBarComponent.getNotificationPanelViewController();
     }
 
     protected void startKeyguard() {
         Trace.beginSection("StatusBar#startKeyguard");
         mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
         mStatusBarKeyguardViewManager.registerStatusBar(
-                /* statusBar= */ this, getBouncerContainer(),
-                mNotificationPanelViewController, mBiometricUnlockController,
-                mDismissCallbackRegistry, mStatusBarWindow.findViewById(R.id.lock_icon_container),
-                mStackScroller, mKeyguardBypassController, mFalsingManager);
+                /* statusBar= */ this, getBouncerContainer(), mNotificationPanel,
+                mBiometricUnlockController, mDismissCallbackRegistry,
+                mStatusBarWindow.findViewById(R.id.lock_icon_container), mStackScroller,
+                mKeyguardBypassController, mFalsingManager);
         mKeyguardIndicationController
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -1488,7 +1488,7 @@
                 && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)
                 && !mDozing
                 && !ONLY_CORE_APPS;
-        mNotificationPanelViewController.setQsExpansionEnabled(expandEnabled);
+        mNotificationPanel.setQsExpansionEnabled(expandEnabled);
         Log.d(TAG, "updateQsExpansionEnabled - QS Expand enabled: " + expandEnabled);
     }
 
@@ -1648,7 +1648,7 @@
 
     public void setQsExpanded(boolean expanded) {
         mStatusBarWindowController.setQsExpanded(expanded);
-        mNotificationPanelViewController.setStatusAccessibilityImportance(expanded
+        mNotificationPanel.setStatusAccessibilityImportance(expanded
                 ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
                 : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
         if (getNavigationBarView() != null) {
@@ -1682,22 +1682,22 @@
         if (inPinnedMode) {
             mStatusBarWindowController.setHeadsUpShowing(true);
             mStatusBarWindowController.setForceStatusBarVisible(true);
-            if (mNotificationPanelViewController.isFullyCollapsed()) {
+            if (mNotificationPanel.isFullyCollapsed()) {
                 // We need to ensure that the touchable region is updated before the window will be
                 // resized, in order to not catch any touches. A layout will ensure that
                 // onComputeInternalInsets will be called and after that we can resize the layout. Let's
                 // make sure that the window stays small for one frame until the touchableRegion is set.
-                mNotificationPanelViewController.getView().requestLayout();
+                mNotificationPanel.requestLayout();
                 mStatusBarWindowController.setForceWindowCollapsed(true);
-                mNotificationPanelViewController.getView().post(() -> {
+                mNotificationPanel.post(() -> {
                     mStatusBarWindowController.setForceWindowCollapsed(false);
                 });
             }
         } else {
             boolean bypassKeyguard = mKeyguardBypassController.getBypassEnabled()
                     && mState == StatusBarState.KEYGUARD;
-            if (!mNotificationPanelViewController.isFullyCollapsed()
-                    || mNotificationPanelViewController.isTracking() || bypassKeyguard) {
+            if (!mNotificationPanel.isFullyCollapsed() || mNotificationPanel.isTracking()
+                    || bypassKeyguard) {
                 // We are currently tracking or is open and the shade doesn't need to be kept
                 // open artificially.
                 mStatusBarWindowController.setHeadsUpShowing(false);
@@ -1708,7 +1708,7 @@
                 // we need to keep the panel open artificially, let's wait until the animation
                 // is finished.
                 mHeadsUpManager.setHeadsUpGoingAway(true);
-                mNotificationPanelViewController.runAfterAnimationFinished(() -> {
+                mNotificationPanel.runAfterAnimationFinished(() -> {
                     if (!mHeadsUpManager.hasPinnedHeadsUp()) {
                         mStatusBarWindowController.setHeadsUpShowing(false);
                         mHeadsUpManager.setHeadsUpGoingAway(false);
@@ -1761,7 +1761,7 @@
     }
 
     public boolean hideStatusBarIconsWhenExpanded() {
-        return mNotificationPanelViewController.hideStatusBarIconsWhenExpanded();
+        return mNotificationPanel.hideStatusBarIconsWhenExpanded();
     }
 
     @Override
@@ -1951,21 +1951,19 @@
 
         if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP == key) {
             mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_UP);
-            mNotificationPanelViewController.collapse(
-                    false /* delayed */, 1.0f /* speedUpFactor */);
+            mNotificationPanel.collapse(false /* delayed */, 1.0f /* speedUpFactor */);
         } else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key) {
             mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
-            if (mNotificationPanelViewController.isFullyCollapsed()) {
+            if (mNotificationPanel.isFullyCollapsed()) {
                 if (mVibrateOnOpening) {
                     mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
                 }
-                mNotificationPanelViewController.expand(true /* animate */);
+                mNotificationPanel.expand(true /* animate */);
                 ((NotificationListContainer) mStackScroller).setWillExpand(true);
                 mHeadsUpManager.unpinAll(true /* userUnpinned */);
                 mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN, 1);
-            } else if (!mNotificationPanelViewController.isInSettings()
-                    && !mNotificationPanelViewController.isExpanding()) {
-                mNotificationPanelViewController.flingSettings(0 /* velocity */,
+            } else if (!mNotificationPanel.isInSettings() && !mNotificationPanel.isExpanding()){
+                mNotificationPanel.flingSettings(0 /* velocity */,
                         NotificationPanelView.FLING_EXPAND);
                 mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN_QS, 1);
             }
@@ -2060,9 +2058,9 @@
         }
 
         if (start) {
-            mNotificationPanelViewController.startWaitingForOpenPanelGesture();
+            mNotificationPanel.startWaitingForOpenPanelGesture();
         } else {
-            mNotificationPanelViewController.stopWaitingForOpenPanelGesture(velocity);
+            mNotificationPanel.stopWaitingForOpenPanelGesture(velocity);
         }
     }
 
@@ -2073,7 +2071,7 @@
             return ;
         }
 
-        mNotificationPanelViewController.expandWithoutQs();
+        mNotificationPanel.expandWithoutQs();
 
         if (false) postStartTracing();
     }
@@ -2091,7 +2089,7 @@
         if (subPanel != null) {
             mQSPanel.openDetails(subPanel);
         }
-        mNotificationPanelViewController.expandWithQs();
+        mNotificationPanel.expandWithQs();
 
         if (false) postStartTracing();
     }
@@ -2114,7 +2112,7 @@
         mStatusBarView.collapsePanel(/*animate=*/ false, false /* delayed*/,
                 1.0f /* speedUpFactor */);
 
-        mNotificationPanelViewController.closeQs();
+        mNotificationPanel.closeQs();
 
         mExpandedVisible = false;
         visibilityChanged(false);
@@ -2135,8 +2133,7 @@
             Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen");
         }
         mCommandQueue.recomputeDisableFlags(
-                mDisplayId,
-                mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
+                mDisplayId, mNotificationPanel.hideStatusBarIconsWhenExpanded() /* animate */);
 
         // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
         // the bouncer appear animation.
@@ -2317,12 +2314,12 @@
                     batteryLevel, new WirelessChargingAnimation.Callback() {
                         @Override
                         public void onAnimationStarting() {
-                            CrossFadeHelper.fadeOut(mNotificationPanelViewController.getView(), 1);
+                            CrossFadeHelper.fadeOut(mNotificationPanel, 1);
                         }
 
                         @Override
                         public void onAnimationEnded() {
-                            CrossFadeHelper.fadeIn(mNotificationPanelViewController.getView());
+                            CrossFadeHelper.fadeIn(mNotificationPanel);
                         }
                     }, mDozing).show();
         } else {
@@ -2351,7 +2348,7 @@
 
     // Called by NavigationBarFragment
     void setQsScrimEnabled(boolean scrimEnabled) {
-        mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled);
+        mNotificationPanel.setQsScrimEnabled(scrimEnabled);
     }
 
     void checkBarMode(@TransitionMode int mode, @WindowVisibleState int windowState,
@@ -2443,12 +2440,11 @@
         }
 
         pw.println("  Panels: ");
-        if (mNotificationPanelViewController != null) {
-            pw.println("    mNotificationPanel="
-                    + mNotificationPanelViewController.getView() + " params="
-                    + mNotificationPanelViewController.getView().getLayoutParams().debug(""));
+        if (mNotificationPanel != null) {
+            pw.println("    mNotificationPanel=" +
+                mNotificationPanel + " params=" + mNotificationPanel.getLayoutParams().debug(""));
             pw.print  ("      ");
-            mNotificationPanelViewController.dump(fd, pw, args);
+            mNotificationPanel.dump(fd, pw, args);
         }
         pw.println("  mStackScroller: ");
         if (mStackScroller instanceof Dumpable) {
@@ -2661,8 +2657,7 @@
                     // Do it after DismissAction has been processed to conserve the needed ordering.
                     mHandler.post(mShadeController::runPostCollapseRunnables);
                 }
-            } else if (isInLaunchTransition()
-                    && mNotificationPanelViewController.isLaunchTransitionFinished()) {
+            } else if (isInLaunchTransition() && mNotificationPanel.isLaunchTransitionFinished()) {
 
                 // We are not dismissing the shade, but the launch transition is already finished,
                 // so nobody will call readyForKeyguardDone anymore. Post it such that
@@ -2819,8 +2814,8 @@
         if (mStatusBarView != null) {
             mStatusBarView.updateResources();
         }
-        if (mNotificationPanelViewController != null) {
-            mNotificationPanelViewController.updateResources();
+        if (mNotificationPanel != null) {
+            mNotificationPanel.updateResources();
         }
         if (mBrightnessMirrorController != null) {
             mBrightnessMirrorController.updateResources();
@@ -3112,7 +3107,7 @@
     public void showKeyguardImpl() {
         mIsKeyguard = true;
         if (mKeyguardStateController.isLaunchTransitionFadingAway()) {
-            mNotificationPanelViewController.cancelAnimation();
+            mNotificationPanel.animate().cancel();
             onLaunchTransitionFadingEnded();
         }
         mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
@@ -3139,8 +3134,8 @@
     }
 
     private void onLaunchTransitionFadingEnded() {
-        mNotificationPanelViewController.setAlpha(1.0f);
-        mNotificationPanelViewController.onAffordanceLaunchEnded();
+        mNotificationPanel.setAlpha(1.0f);
+        mNotificationPanel.onAffordanceLaunchEnded();
         releaseGestureWakeLock();
         runLaunchTransitionEndRunnable();
         mKeyguardStateController.setLaunchTransitionFadingAway(false);
@@ -3148,8 +3143,8 @@
     }
 
     public boolean isInLaunchTransition() {
-        return mNotificationPanelViewController.isLaunchTransitionRunning()
-                || mNotificationPanelViewController.isLaunchTransitionFinished();
+        return mNotificationPanel.isLaunchTransitionRunning()
+                || mNotificationPanel.isLaunchTransitionFinished();
     }
 
     /**
@@ -3170,15 +3165,18 @@
             }
             updateScrimController();
             mPresenter.updateMediaMetaData(false, true);
-            mNotificationPanelViewController.setAlpha(1);
-            mNotificationPanelViewController.fadeOut(
-                    FADE_KEYGUARD_START_DELAY, FADE_KEYGUARD_DURATION,
-                    this::onLaunchTransitionFadingEnded);
+            mNotificationPanel.setAlpha(1);
+            mNotificationPanel.animate()
+                    .alpha(0)
+                    .setStartDelay(FADE_KEYGUARD_START_DELAY)
+                    .setDuration(FADE_KEYGUARD_DURATION)
+                    .withLayer()
+                    .withEndAction(this::onLaunchTransitionFadingEnded);
             mCommandQueue.appTransitionStarting(mDisplayId, SystemClock.uptimeMillis(),
                     LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
         };
-        if (mNotificationPanelViewController.isLaunchTransitionRunning()) {
-            mNotificationPanelViewController.setLaunchTransitionEndRunnable(hideRunnable);
+        if (mNotificationPanel.isLaunchTransitionRunning()) {
+            mNotificationPanel.setLaunchTransitionEndRunnable(hideRunnable);
         } else {
             hideRunnable.run();
         }
@@ -3189,18 +3187,22 @@
      * fading.
      */
     public void fadeKeyguardWhilePulsing() {
-        mNotificationPanelViewController.fadeOut(0, FADE_KEYGUARD_DURATION_PULSING,
-                ()-> {
-                hideKeyguard();
-                mStatusBarKeyguardViewManager.onKeyguardFadedAway();
-            }).start();
+        mNotificationPanel.animate()
+                .alpha(0f)
+                .setStartDelay(0)
+                .setDuration(FADE_KEYGUARD_DURATION_PULSING)
+                .setInterpolator(Interpolators.ALPHA_OUT)
+                .withEndAction(()-> {
+                    hideKeyguard();
+                    mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+                }).start();
     }
 
     /**
      * Plays the animation when an activity that was occluding Keyguard goes away.
      */
     public void animateKeyguardUnoccluding() {
-        mNotificationPanelViewController.setExpandedFraction(0f);
+        mNotificationPanel.setExpandedFraction(0f);
         animateExpandNotificationsPanel();
     }
 
@@ -3216,9 +3218,9 @@
 
     private void onLaunchTransitionTimeout() {
         Log.w(TAG, "Launch transition: Timeout!");
-        mNotificationPanelViewController.onAffordanceLaunchEnded();
+        mNotificationPanel.onAffordanceLaunchEnded();
         releaseGestureWakeLock();
-        mNotificationPanelViewController.resetViews(false /* animate */);
+        mNotificationPanel.resetViews(false /* animate */);
     }
 
     private void runLaunchTransitionEndRunnable() {
@@ -3251,7 +3253,7 @@
                 mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
             }
             long delay = mKeyguardStateController.calculateGoingToFullShadeDelay();
-            mNotificationPanelViewController.animateToFullShade(delay);
+            mNotificationPanel.animateToFullShade(delay);
             if (mDraggedDownEntry != null) {
                 mDraggedDownEntry.setUserLocked(false);
                 mDraggedDownEntry = null;
@@ -3260,7 +3262,7 @@
             // Disable layout transitions in navbar for this transition because the load is just
             // too heavy for the CPU and GPU on any device.
             mNavigationBarController.disableAnimationsDuringHide(mDisplayId, delay);
-        } else if (!mNotificationPanelViewController.isCollapsing()) {
+        } else if (!mNotificationPanel.isCollapsing()) {
             instantCollapseNotificationPanel();
         }
 
@@ -3271,10 +3273,10 @@
         }
         mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
         releaseGestureWakeLock();
-        mNotificationPanelViewController.onAffordanceLaunchEnded();
-        mNotificationPanelViewController.cancelAnimation();
-        mNotificationPanelViewController.setAlpha(1f);
-        mNotificationPanelViewController.resetViewGroupFade();
+        mNotificationPanel.onAffordanceLaunchEnded();
+        mNotificationPanel.animate().cancel();
+        mNotificationPanel.setAlpha(1f);
+        ViewGroupFadeHelper.reset(mNotificationPanel);
         updateScrimController();
         Trace.endSection();
         return staying;
@@ -3349,7 +3351,7 @@
         boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock)
                 || (mDozing && mDozeServiceHost.shouldAnimateScreenOff() && sleepingFromKeyguard);
 
-        mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation);
+        mNotificationPanel.setDozing(mDozing, animate, mWakeUpTouchLocation);
         updateQsExpansionEnabled();
         Trace.endSection();
     }
@@ -3381,27 +3383,27 @@
 
     public void endAffordanceLaunch() {
         releaseGestureWakeLock();
-        mNotificationPanelViewController.onAffordanceLaunchEnded();
+        mNotificationPanel.onAffordanceLaunchEnded();
     }
 
     public boolean onBackPressed() {
         boolean isScrimmedBouncer = mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED;
         if (mStatusBarKeyguardViewManager.onBackPressed(isScrimmedBouncer /* hideImmediately */)) {
             if (!isScrimmedBouncer) {
-                mNotificationPanelViewController.expandWithoutQs();
+                mNotificationPanel.expandWithoutQs();
             }
             return true;
         }
-        if (mNotificationPanelViewController.isQsExpanded()) {
-            if (mNotificationPanelViewController.isQsDetailShowing()) {
-                mNotificationPanelViewController.closeQsDetail();
+        if (mNotificationPanel.isQsExpanded()) {
+            if (mNotificationPanel.isQsDetailShowing()) {
+                mNotificationPanel.closeQsDetail();
             } else {
-                mNotificationPanelViewController.animateCloseQs(false /* animateAway */);
+                mNotificationPanel.animateCloseQs(false /* animateAway */);
             }
             return true;
         }
         if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
-            if (mNotificationPanelViewController.canPanelBeCollapsed()) {
+            if (mNotificationPanel.canPanelBeCollapsed()) {
                 mShadeController.animateCollapsePanels();
             } else {
                 mBubbleController.performBackPressIfNeeded();
@@ -3431,7 +3433,7 @@
     }
 
     void instantCollapseNotificationPanel() {
-        mNotificationPanelViewController.instantCollapse();
+        mNotificationPanel.instantCollapse();
         mShadeController.runPostCollapseRunnables();
     }
 
@@ -3497,7 +3499,7 @@
         // Collapse the notification panel if open
         boolean dozingAnimated = mDozeServiceHost.getDozingRequested()
                 && mDozeParameters.shouldControlScreenOff();
-        mNotificationPanelViewController.resetViews(dozingAnimated);
+        mNotificationPanel.resetViews(dozingAnimated);
 
         updateQsExpansionEnabled();
         mKeyguardViewMediator.setDozing(mDozing);
@@ -3571,7 +3573,7 @@
      * @return bottom area view
      */
     public KeyguardBottomAreaView getKeyguardBottomAreaView() {
-        return mNotificationPanelViewController.getKeyguardBottomAreaView();
+        return mNotificationPanel.getKeyguardBottomAreaView();
     }
 
     /**
@@ -3610,7 +3612,7 @@
             mDraggedDownEntry = entry;
             mPendingRemoteInputView = null;
         } else {
-            mNotificationPanelViewController.animateToFullShade(0 /* delay */);
+            mNotificationPanel.animateToFullShade(0 /* delay */);
             mStatusBarStateController.setState(StatusBarState.SHADE_LOCKED);
         }
     }
@@ -3636,7 +3638,7 @@
      * Collapses the notification shade if it is tracking or expanded.
      */
     public void collapseShade() {
-        if (mNotificationPanelViewController.isTracking()) {
+        if (mNotificationPanel.isTracking()) {
             mStatusBarWindowViewController.cancelCurrentTouch();
         }
         if (mPanelExpanded && mState == StatusBarState.SHADE) {
@@ -3648,7 +3650,7 @@
     final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
         @Override
         public void onFinishedGoingToSleep() {
-            mNotificationPanelViewController.onAffordanceLaunchEnded();
+            mNotificationPanel.onAffordanceLaunchEnded();
             releaseGestureWakeLock();
             mLaunchCameraWhenFinishedWaking = false;
             mDeviceInteractive = false;
@@ -3709,8 +3711,7 @@
             mBypassHeadsUpNotifier.setFullyAwake(true);
             mWakeUpCoordinator.setWakingUp(false);
             if (mLaunchCameraWhenFinishedWaking) {
-                mNotificationPanelViewController.launchCamera(
-                        false /* animate */, mLastCameraLaunchSource);
+                mNotificationPanel.launchCamera(false /* animate */, mLastCameraLaunchSource);
                 mLaunchCameraWhenFinishedWaking = false;
             }
             updateScrimController();
@@ -3727,7 +3728,7 @@
                 && !mDozeParameters.shouldControlScreenOff();
         boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing())
                 || goingToSleepWithoutAnimation;
-        mNotificationPanelViewController.setTouchAndAnimationDisabled(disabled);
+        mNotificationPanel.setTouchAndAnimationDisabled(disabled);
         mNotificationIconAreaController.setAnimationsEnabled(!disabled);
     }
 
@@ -3735,7 +3736,7 @@
         @Override
         public void onScreenTurningOn() {
             mFalsingManager.onScreenTurningOn();
-            mNotificationPanelViewController.onScreenTurningOn();
+            mNotificationPanel.onScreenTurningOn();
         }
 
         @Override
@@ -3804,7 +3805,7 @@
             mLaunchCameraOnFinishedGoingToSleep = true;
             return;
         }
-        if (!mNotificationPanelViewController.canCameraGestureBeLaunched()) {
+        if (!mNotificationPanel.canCameraGestureBeLaunched()) {
             if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Can't launch camera right now");
             return;
         }
@@ -3834,8 +3835,7 @@
                 if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
                     mStatusBarKeyguardViewManager.reset(true /* hide */);
                 }
-                mNotificationPanelViewController.launchCamera(
-                        mDeviceInteractive /* animate */, source);
+                mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source);
                 updateScrimController();
             } else {
                 // We need to defer the camera launch until the screen comes on, since otherwise
@@ -3894,7 +3894,7 @@
                 !mBiometricUnlockController.isBiometricUnlock());
 
         boolean launchingAffordanceWithPreview =
-                mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
+                mNotificationPanel.isLaunchingAffordanceWithPreview();
         mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
 
         if (mBouncerShowing) {
@@ -4243,7 +4243,7 @@
      * When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
      */
     public void onBouncerPreHideAnimation() {
-        mNotificationPanelViewController.onBouncerPreHideAnimation();
+        mNotificationPanel.onBouncerPreHideAnimation();
         mLockscreenLockIconController.onBouncerPreHideAnimation();
     }
 
@@ -4286,8 +4286,8 @@
         mAssistManagerLazy.get().showDisclosure();
     }
 
-    public NotificationPanelViewController getPanelController() {
-        return mNotificationPanelViewController;
+    public NotificationPanelView getPanel() {
+        return mNotificationPanel;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarComponent.java
similarity index 74%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarComponent.java
index 21d0bb8..f3c843c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarComponent.java
@@ -14,14 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.phone.dagger;
+package com.android.systemui.statusbar.phone;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
-import com.android.systemui.statusbar.phone.StatusBarWindowView;
-import com.android.systemui.statusbar.phone.StatusBarWindowViewController;
-
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 
@@ -33,8 +29,7 @@
 /**
  * Dagger subcomponent tied to the lifecycle of StatusBar views.
  */
-@Subcomponent(modules = {StatusBarViewModule.class})
-@StatusBarComponent.StatusBarScope
+@Subcomponent
 public interface StatusBarComponent {
     /**
      * Builder for {@link StatusBarComponent}.
@@ -59,10 +54,4 @@
     @StatusBarScope
     StatusBarWindowViewController getStatusBarWindowViewController();
 
-    /**
-     * Creates a NotificationPanelViewController.
-     */
-    @StatusBarScope
-    NotificationPanelViewController getNotificationPanelViewController();
-
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 407d256..f51174b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -133,7 +133,7 @@
     protected LockPatternUtils mLockPatternUtils;
     protected ViewMediatorCallback mViewMediatorCallback;
     protected StatusBar mStatusBar;
-    private NotificationPanelViewController mNotificationPanelViewController;
+    private NotificationPanelView mNotificationPanelView;
     private BiometricUnlockController mBiometricUnlockController;
 
     private ViewGroup mContainer;
@@ -224,7 +224,7 @@
 
     public void registerStatusBar(StatusBar statusBar,
             ViewGroup container,
-            NotificationPanelViewController notificationPanelViewController,
+            NotificationPanelView notificationPanelView,
             BiometricUnlockController biometricUnlockController,
             DismissCallbackRegistry dismissCallbackRegistry,
             ViewGroup lockIconContainer, View notificationContainer,
@@ -239,8 +239,8 @@
         mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                 mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry,
                 mExpansionCallback, mKeyguardStateController, falsingManager, bypassController);
-        mNotificationPanelViewController = notificationPanelViewController;
-        notificationPanelViewController.addExpansionListener(this);
+        mNotificationPanelView = notificationPanelView;
+        notificationPanelView.addExpansionListener(this);
         mBypassController = bypassController;
         mNotificationContainer = notificationContainer;
     }
@@ -253,7 +253,7 @@
         // • The user quickly taps on the display and we show "swipe up to unlock."
         // • Keyguard will be dismissed by an action. a.k.a: FLAG_DISMISS_KEYGUARD_ACTIVITY
         // • Full-screen user switcher is displayed.
-        if (mNotificationPanelViewController.isUnlockHintRunning()) {
+        if (mNotificationPanelView.isUnlockHintRunning()) {
             mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
         } else if (bouncerNeedsScrimming()) {
             mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
@@ -284,7 +284,7 @@
             return;
         }
         boolean keyguardWithoutQs = mStatusBarStateController.getState() == StatusBarState.KEYGUARD
-                && !mNotificationPanelViewController.isQsExpanded();
+                && !mNotificationPanelView.isQsExpanded();
         boolean lockVisible = (mBouncer.isShowing() || keyguardWithoutQs)
                 && !mBouncer.isAnimatingAway() && !mKeyguardStateController.isKeyguardFadingAway();
 
@@ -555,7 +555,7 @@
         } else if (finishRunnable != null) {
             finishRunnable.run();
         }
-        mNotificationPanelViewController.blockExpansionForCurrentTouch();
+        mNotificationPanelView.blockExpansionForCurrentTouch();
         updateLockIcon();
     }
 
@@ -609,8 +609,7 @@
             hideBouncer(true /* destroyView */);
             if (wakeUnlockPulsing) {
                 if (needsFading) {
-                    ViewGroupFadeHelper.fadeOutAllChildrenExcept(
-                            mNotificationPanelViewController.getView(),
+                    ViewGroupFadeHelper.fadeOutAllChildrenExcept(mNotificationPanelView,
                             mNotificationContainer,
                             fadeoutDuration,
                                     () -> {
@@ -626,8 +625,7 @@
                 if (!staying) {
                     mStatusBarWindowController.setKeyguardFadingAway(true);
                     if (needsFading) {
-                        ViewGroupFadeHelper.fadeOutAllChildrenExcept(
-                                mNotificationPanelViewController.getView(),
+                        ViewGroupFadeHelper.fadeOutAllChildrenExcept(mNotificationPanelView,
                                 mNotificationContainer,
                                 fadeoutDuration,
                                 () -> {
@@ -686,7 +684,7 @@
     public void onKeyguardFadedAway() {
         mContainer.postDelayed(() -> mStatusBarWindowController.setKeyguardFadingAway(false),
                 100);
-        ViewGroupFadeHelper.reset(mNotificationPanelViewController.getView());
+        ViewGroupFadeHelper.reset(mNotificationPanelView);
         mStatusBar.finishKeyguardFadingAway();
         mBiometricUnlockController.finishKeyguardFadingAway();
         WindowManagerGlobal.getInstance().trimMemory(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
index 153ca22..12033de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
@@ -69,7 +69,6 @@
 import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
-import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 0f3b5db..661a7b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -61,6 +61,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -101,7 +102,7 @@
     private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
     private final MetricsLogger mMetricsLogger;
     private final Context mContext;
-    private final NotificationPanelViewController mNotificationPanel;
+    private final NotificationPanelView mNotificationPanel;
     private final NotificationPresenter mPresenter;
     private final LockPatternUtils mLockPatternUtils;
     private final HeadsUpManagerPhone mHeadsUpManager;
@@ -120,7 +121,7 @@
     private boolean mIsCollapsingToShowActivityOverLockscreen;
 
     private StatusBarNotificationActivityStarter(Context context, CommandQueue commandQueue,
-            Lazy<AssistManager> assistManagerLazy, NotificationPanelViewController panel,
+            Lazy<AssistManager> assistManagerLazy, NotificationPanelView panel,
             NotificationPresenter presenter, NotificationEntryManager entryManager,
             HeadsUpManagerPhone headsUpManager, ActivityStarter activityStarter,
             ActivityLaunchAnimator activityLaunchAnimator, IStatusBarService statusBarService,
@@ -518,6 +519,7 @@
         private final NotificationGroupManager mGroupManager;
         private final NotificationLockscreenUserManager mLockscreenUserManager;
         private final KeyguardStateController mKeyguardStateController;
+        private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
         private final MetricsLogger mMetricsLogger;
         private final LockPatternUtils mLockPatternUtils;
         private final Handler mMainThreadHandler;
@@ -525,8 +527,7 @@
         private final Executor mUiBgExecutor;
         private final ActivityIntentHelper mActivityIntentHelper;
         private final BubbleController mBubbleController;
-        private NotificationPanelViewController mNotificationPanelViewController;
-        private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
+        private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
         private final ShadeController mShadeController;
         private NotificationPresenter mNotificationPresenter;
         private ActivityLaunchAnimator mActivityLaunchAnimator;
@@ -557,7 +558,8 @@
                 @UiBackground Executor uiBgExecutor,
                 ActivityIntentHelper activityIntentHelper,
                 BubbleController bubbleController,
-                ShadeController shadeController) {
+                ShadeController shadeController,
+                SuperStatusBarViewFactory superStatusBarViewFactory) {
             mContext = context;
             mCommandQueue = commandQueue;
             mAssistManagerLazy = assistManagerLazy;
@@ -583,6 +585,7 @@
             mActivityIntentHelper = activityIntentHelper;
             mBubbleController = bubbleController;
             mShadeController = shadeController;
+            mSuperStatusBarViewFactory = superStatusBarViewFactory;
         }
 
         /** Sets the status bar to use as {@link StatusBar}. */
@@ -601,19 +604,10 @@
             return this;
         }
 
-        /** Set the NotificationPanelViewController */
-        public Builder setNotificationPanelViewController(
-                NotificationPanelViewController notificationPanelViewController) {
-            mNotificationPanelViewController = notificationPanelViewController;
-            return this;
-        }
-
-
-
         public StatusBarNotificationActivityStarter build() {
             return new StatusBarNotificationActivityStarter(mContext,
                     mCommandQueue, mAssistManagerLazy,
-                    mNotificationPanelViewController,
+                    mSuperStatusBarViewFactory.getNotificationPanelView(),
                     mNotificationPresenter,
                     mEntryManager,
                     mHeadsUpManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 0fd0dab..ab9d540 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -107,7 +107,7 @@
     private final NotificationGutsManager mGutsManager =
             Dependency.get(NotificationGutsManager.class);
 
-    private final NotificationPanelViewController mNotificationPanel;
+    private final NotificationPanelView mNotificationPanel;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final AboveShelfObserver mAboveShelfObserver;
     private final DozeScrimController mDozeScrimController;
@@ -132,7 +132,7 @@
     private int mMaxKeyguardNotifications;
 
     public StatusBarNotificationPresenter(Context context,
-            NotificationPanelViewController panel,
+            NotificationPanelView panel,
             HeadsUpManagerPhone headsUp,
             StatusBarWindowView statusBarWindow,
             ViewGroup stackScroller,
@@ -172,7 +172,7 @@
                 ServiceManager.getService(Context.STATUS_BAR_SERVICE));
 
         if (MULTIUSER_DEBUG) {
-            mNotificationPanelDebugText = mNotificationPanel.getHeaderDebugInfo();
+            mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
             mNotificationPanelDebugText.setVisibility(View.VISIBLE);
         }
 
@@ -217,7 +217,7 @@
             mEntryManager.addNotificationLifetimeExtenders(
                     remoteInputManager.getLifetimeExtenders());
             notificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager,
-                    mEntryManager, this);
+                    this);
             mNotificationInterruptionStateProvider.setUpWithPresenter(
                     this, mHeadsUpManager, this::canHeadsUp);
             mLockscreenUserManager.setUpWithPresenter(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 1e3c5d6..6b51391 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -75,10 +75,6 @@
         setMotionEventSplittingEnabled(false);
     }
 
-    public NotificationPanelView getNotificationPanelView() {
-        return findViewById(R.id.notification_panel);
-    }
-
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
         final Insets insets = windowInsets.getMaxInsets(WindowInsets.Type.systemBars());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
index 4935f0e..eb86bcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
@@ -26,9 +26,11 @@
 import android.view.GestureDetector;
 import android.view.InputDevice;
 import android.view.KeyEvent;
+import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewStub;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.ExpandHelper;
@@ -91,7 +93,6 @@
     private boolean mSingleTapEnabled;
     private boolean mExpandingBelowNotch;
     private final DockManager mDockManager;
-    private final NotificationPanelViewController mNotificationPanelViewController;
 
     @Inject
     public StatusBarWindowViewController(
@@ -112,8 +113,7 @@
             CommandQueue commandQueue,
             ShadeController shadeController,
             DockManager dockManager,
-            StatusBarWindowView statusBarWindowView,
-            NotificationPanelViewController notificationPanelViewController) {
+            StatusBarWindowView statusBarWindowView) {
         mInjectionInflationController = injectionInflationController;
         mCoordinator = coordinator;
         mPulseExpansionHandler = pulseExpansionHandler;
@@ -132,7 +132,6 @@
         mView = statusBarWindowView;
         mShadeController = shadeController;
         mDockManager = dockManager;
-        mNotificationPanelViewController = notificationPanelViewController;
 
         // This view is not part of the newly inflated expanded status bar.
         mBrightnessMirror = mView.findViewById(R.id.brightness_mirror);
@@ -140,6 +139,39 @@
 
     /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
     public void setupExpandedStatusBar() {
+        // TODO: create controller for NotificationPanelView
+        NotificationPanelView notificationPanelView = new NotificationPanelView(
+                mView.getContext(),
+                null,
+                mInjectionInflationController,
+                mCoordinator,
+                mPulseExpansionHandler,
+                mDynamicPrivacyController,
+                mBypassController,
+                mFalsingManager,
+                mPluginManager,
+                mShadeController,
+                mNotificationLockscreenUserManager,
+                mNotificationEntryManager,
+                mKeyguardStateController,
+                mStatusBarStateController,
+                mDozeLog,
+                mDozeParameters,
+                mCommandQueue);
+        ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+        notificationPanelView.setVisibility(View.INVISIBLE);
+        notificationPanelView.setId(R.id.notification_panel);
+        LayoutInflater li = mInjectionInflationController.injectable(
+                LayoutInflater.from(mView.getContext()));
+
+        li.inflate(R.layout.status_bar_expanded, notificationPanelView);
+        notificationPanelView.onChildrenAttached();
+
+        ViewStub statusBarExpanded = mView.findViewById(R.id.status_bar_expanded);
+        mView.addView(notificationPanelView, mView.indexOfChild(statusBarExpanded), lp);
+        mView.removeView(statusBarExpanded);
+
         mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
 
         TunerService.Tunable tunable = (key, newValue) -> {
@@ -201,8 +233,8 @@
                 if (!isCancel && mService.shouldIgnoreTouch()) {
                     return false;
                 }
-                if (isDown && mNotificationPanelViewController.isFullyCollapsed()) {
-                    mNotificationPanelViewController.startExpandLatencyTracking();
+                if (isDown && notificationPanelView.isFullyCollapsed()) {
+                    notificationPanelView.startExpandLatencyTracking();
                 }
                 if (isDown) {
                     setTouchActive(true);
@@ -255,7 +287,7 @@
                     return true;
                 }
                 boolean intercept = false;
-                if (mNotificationPanelViewController.isFullyExpanded()
+                if (notificationPanelView.isFullyExpanded()
                         && mDragDownHelper.isDragDownEnabled()
                         && !mService.isBouncerShowing()
                         && !mStatusBarStateController.isDozing()) {
@@ -271,7 +303,7 @@
                 MotionEvent cancellation = MotionEvent.obtain(ev);
                 cancellation.setAction(MotionEvent.ACTION_CANCEL);
                 mStackScrollLayout.onInterceptTouchEvent(cancellation);
-                mNotificationPanelViewController.getView().onInterceptTouchEvent(cancellation);
+                notificationPanelView.onInterceptTouchEvent(cancellation);
                 cancellation.recycle();
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
deleted file mode 100644
index 20bd51d..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone.dagger;
-
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-import com.android.systemui.statusbar.phone.StatusBarWindowView;
-
-import dagger.Module;
-import dagger.Provides;
-
-@Module
-public abstract class StatusBarViewModule {
-    /** */
-    @Provides
-    @StatusBarComponent.StatusBarScope
-    public static NotificationPanelView getNotificationPanelView(
-            StatusBarWindowView statusBarWindowView) {
-        return statusBarWindowView.getNotificationPanelView();
-    }
-
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 625d884..88edf8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -24,7 +24,7 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
 import com.android.systemui.statusbar.phone.StatusBarWindowView;
 
 import java.util.Objects;
@@ -38,17 +38,16 @@
 
     private final StatusBarWindowView mStatusBarWindow;
     private final Consumer<Boolean> mVisibilityCallback;
-    private final NotificationPanelViewController mNotificationPanel;
+    private final NotificationPanelView mNotificationPanel;
     private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
     private final int[] mInt2Cache = new int[2];
     private View mBrightnessMirror;
 
     public BrightnessMirrorController(StatusBarWindowView statusBarWindow,
-            NotificationPanelViewController notificationPanelViewController,
             @NonNull Consumer<Boolean> visibilityCallback) {
         mStatusBarWindow = statusBarWindow;
         mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
-        mNotificationPanel = notificationPanelViewController;
+        mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel);
         mNotificationPanel.setPanelAlphaEndAction(() -> {
             mBrightnessMirror.setVisibility(View.INVISIBLE);
         });
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index d28a6699..4b283fed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -35,7 +35,7 @@
 import com.android.systemui.R;
 import com.android.systemui.qs.tiles.UserDetailItemView;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
 
 /**
  * Manages the user switcher on the Keyguard.
@@ -57,8 +57,7 @@
     private boolean mAnimating;
 
     public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
-            KeyguardStatusBarView statusBarView,
-            NotificationPanelViewController panelViewController) {
+            KeyguardStatusBarView statusBarView, NotificationPanelView panelView) {
         boolean keyguardUserSwitcherEnabled =
                 context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher) || ALWAYS_ON;
         UserSwitcherController userSwitcherController = Dependency.get(UserSwitcherController.class);
@@ -68,7 +67,7 @@
             reinflateViews();
             mStatusBarView = statusBarView;
             mStatusBarView.setKeyguardUserSwitcher(this);
-            panelViewController.setKeyguardUserSwitcher(this);
+            panelView.setKeyguardUserSwitcher(this);
             mAdapter = new Adapter(context, userSwitcherController, this);
             mAdapter.registerDataSetObserver(mDataSetObserver);
             mUserSwitcherController = userSwitcherController;
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 56aae17..6976649 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -35,6 +35,7 @@
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.LockIcon;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -128,6 +129,11 @@
         NotificationStackScrollLayout createNotificationStackScrollLayout();
 
         /**
+         * Creates the NotificationPanelView.
+         */
+        NotificationPanelView createPanelView();
+
+        /**
          * Creates the Shelf.
          */
         NotificationShelf creatNotificationShelf();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 40b0ba9..77659df 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -345,7 +345,7 @@
         NotificationContentInflater.InflationCallback callback =
                 new NotificationContentInflater.InflationCallback() {
                     @Override
-                    public void handleInflationException(StatusBarNotification notification,
+                    public void handleInflationException(NotificationEntry entry,
                             Exception e) {
                         countDownLatch.countDown();
                     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
index a54f733..145a25c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
@@ -33,7 +33,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
 import com.android.systemui.statusbar.phone.StatusBarWindowView;
 import com.android.systemui.statusbar.phone.StatusBarWindowViewController;
 
@@ -64,7 +64,7 @@
         mLaunchAnimator = new ActivityLaunchAnimator(
                 mStatusBarWindowViewController,
                 mCallback,
-                mock(NotificationPanelViewController.class),
+                mock(NotificationPanelView.class),
                 mNotificationContainer);
 
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index f55ea4f..c559265 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -65,6 +65,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -143,6 +144,7 @@
     @Mock private RowInflaterTask mAsyncInflationTask;
     @Mock private NotificationRowBinder mMockedRowBinder;
     @Mock private NotifLog mNotifLog;
+    @Mock private FeatureFlags mFeatureFlags;
 
     private int mId;
     private NotificationEntry mEntry;
@@ -219,6 +221,8 @@
 
         mEntry.expandedIcon = mock(StatusBarIconView.class);
 
+        when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
+        when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
         mEntryManager = new TestableNotificationEntryManager(
                 mNotifLog,
                 mGroupManager,
@@ -230,7 +234,8 @@
                         mNotifLog,
                         mock(NotificationSectionsFeatureManager.class),
                         mock(PeopleNotificationIdentifier.class)),
-                mEnvironment
+                mEnvironment,
+                mFeatureFlags
         );
         mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager);
         mEntryManager.addNotificationEntryListener(mEntryListener);
@@ -242,7 +247,8 @@
                         mock(StatusBarStateController.class),
                         mock(NotificationLogger.class));
         notificationRowBinder.setUpWithPresenter(
-                mPresenter, mListContainer, mHeadsUpManager, mEntryManager, mBindCallback);
+                mPresenter, mListContainer, mHeadsUpManager, mBindCallback);
+        notificationRowBinder.setInflationCallback(mEntryManager);
         notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class));
         mEntryManager.setRowBinder(notificationRowBinder);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
index 34beefe..1afee12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification
 
+import com.android.systemui.statusbar.FeatureFlags
 import com.android.systemui.statusbar.NotificationPresenter
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager
@@ -33,8 +34,9 @@
     log: NotifLog,
     gm: NotificationGroupManager,
     rm: NotificationRankingManager,
-    ke: KeyguardEnvironment
-) : NotificationEntryManager(log, gm, rm, ke) {
+    ke: KeyguardEnvironment,
+    ff: FeatureFlags
+) : NotificationEntryManager(log, gm, rm, ke, ff) {
 
     public var countDownLatch: CountDownLatch = CountDownLatch(1)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
index 0764d0c..867a9b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
@@ -178,9 +178,10 @@
 private fun fakePersonModel(
     id: String,
     name: CharSequence,
-    clickIntent: PendingIntent
+    clickIntent: PendingIntent,
+    userId: Int = 0
 ): PersonModel =
-        PersonModel(id, name, mock(Drawable::class.java), clickIntent)
+        PersonModel(id, name, mock(Drawable::class.java), clickIntent, userId)
 
 private fun fakePersonViewModel(name: CharSequence): PersonViewModel =
         PersonViewModel(name, mock(Drawable::class.java), mock({}.javaClass))
@@ -207,4 +208,4 @@
     override fun onDataChanged(data: T) {
         lastSeen = Maybe.Just(data)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index dc4e498..f916fe5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -33,7 +33,6 @@
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
-import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.ArrayMap;
@@ -179,7 +178,7 @@
                 true /* isNewView */, (v, p, r) -> true,
                 new InflationCallback() {
                     @Override
-                    public void handleInflationException(StatusBarNotification notification,
+                    public void handleInflationException(NotificationEntry entry,
                             Exception e) {
                         countDownLatch.countDown();
                         throw new RuntimeException("No Exception expected");
@@ -261,7 +260,7 @@
         inflater.setInflateSynchronously(true);
         InflationCallback callback = new InflationCallback() {
             @Override
-            public void handleInflationException(StatusBarNotification notification,
+            public void handleInflationException(NotificationEntry entry,
                     Exception e) {
                 if (!expectingException) {
                     exceptionHolder.setException(e);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 77a6a26..39f037c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -53,6 +53,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -166,7 +167,8 @@
                         mock(NotificationSectionsFeatureManager.class),
                         mock(PeopleNotificationIdentifier.class)
                 ),
-                mock(NotificationEntryManager.KeyguardEnvironment.class));
+                mock(NotificationEntryManager.KeyguardEnvironment.class),
+                mock(FeatureFlags.class));
         mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
         mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index ae87eef..8decae3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -64,8 +64,7 @@
         mMockNotificiationAreaController = mock(NotificationIconAreaController.class);
         mNotificationAreaInner = mock(View.class);
         mCenteredNotificationAreaView = mock(View.class);
-        when(statusBar.getPanelController()).thenReturn(
-                mock(NotificationPanelViewController.class));
+        when(statusBar.getPanel()).thenReturn(mock(NotificationPanelView.class));
         when(mNotificationAreaInner.animate()).thenReturn(mock(ViewPropertyAnimator.class));
         when(mMockNotificiationAreaController.getNotificationInnerAreaView()).thenReturn(
                 mNotificationAreaInner);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index d31f175..46f6cfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -86,7 +86,7 @@
     @Mock private NotificationIconAreaController mNotificationIconAreaController;
     @Mock private StatusBarWindowViewController mStatusBarWindowViewController;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock private NotificationPanelViewController mNotificationPanel;
+    @Mock private NotificationPanelView mNotificationPanel;
     @Mock private View mAmbientIndicationContainer;
     @Mock private BiometricUnlockController mBiometricUnlockController;
     @Mock private LockscreenLockIconController mLockscreenLockIconController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 7448dbd..0260269 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -53,8 +53,7 @@
 
     private final NotificationStackScrollLayout mStackScroller =
             mock(NotificationStackScrollLayout.class);
-    private final NotificationPanelViewController mPanelView =
-            mock(NotificationPanelViewController.class);
+    private final NotificationPanelView mPanelView = mock(NotificationPanelView.class);
     private final DarkIconDispatcher mDarkIconDispatcher = mock(DarkIconDispatcher.class);
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     private ExpandableNotificationRow mFirst;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 1f37ad8..c165e56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -16,13 +16,9 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -30,44 +26,37 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.ActivityManager;
 import android.app.StatusBarManager;
-import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.hardware.biometrics.BiometricSourceType;
-import android.os.PowerManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.util.DisplayMetrics;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.util.LatencyTracker;
-import com.android.keyguard.KeyguardClockSwitch;
+import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.R;
+import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.KeyguardAffordanceView;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -81,7 +70,6 @@
 import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
 
 import java.util.function.Consumer;
 
@@ -97,6 +85,8 @@
     @Mock
     private NotificationStackScrollLayout mNotificationStackScrollLayout;
     @Mock
+    private KeyguardStatusView mKeyguardStatusView;
+    @Mock
     private KeyguardBottomAreaView mKeyguardBottomArea;
     @Mock
     private KeyguardBottomAreaView mQsFrame;
@@ -119,89 +109,27 @@
     @Mock
     private PanelBar mPanelBar;
     @Mock
+    private KeyguardAffordanceHelper mAffordanceHelper;
+    @Mock
     private KeyguardUpdateMonitor mUpdateMonitor;
     @Mock
     private FalsingManager mFalsingManager;
     @Mock
     private KeyguardBypassController mKeyguardBypassController;
-    @Mock
-    private DozeParameters mDozeParameters;
-    @Mock
-    private NotificationPanelView mView;
-    @Mock
-    private InjectionInflationController mInjectionInflationController;
-    @Mock
-    private DynamicPrivacyController mDynamicPrivacyController;
-    @Mock
-    private PluginManager mPluginManager;
-    @Mock
-    private ShadeController mShadeController;
-    @Mock
-    private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
-    @Mock
-    private NotificationEntryManager mNotificationEntryManager;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-    @Mock
-    private DozeLog mDozeLog;
-    @Mock
-    private CommandQueue mCommandQueue;
-    @Mock
-    private VibratorHelper mVibratorHelper;
-    @Mock
-    private LatencyTracker mLatencyTracker;
-    @Mock
-    private PowerManager mPowerManager;
-    @Mock
-    private AccessibilityManager mAccessibilityManager;
-    @Mock
-    private MetricsLogger mMetricsLogger;
-    @Mock
-    private ActivityManager mActivityManager;
-    @Mock
-    private Resources mResources;
-    @Mock
-    private Configuration mConfiguration;
-    private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
-    @Mock
-    private KeyguardClockSwitch mKeyguardClockSwitch;
-    private PanelViewController.TouchHandler mTouchHandler;
-    @Mock
-    private ZenModeController mZenModeController;
-    @Mock
-    private ConfigurationController mConfigurationController;
-    private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
-
-    private NotificationPanelViewController mNotificationPanelViewController;
+    @Mock private DozeParameters mDozeParameters;
+    private NotificationPanelView mNotificationPanelView;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        when(mHeadsUpCallback.getContext()).thenReturn(mContext);
-        when(mView.getResources()).thenReturn(mResources);
-        when(mResources.getConfiguration()).thenReturn(mConfiguration);
-        mConfiguration.orientation = ORIENTATION_PORTRAIT;
-        when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
-        mDisplayMetrics.density = 100;
-        when(mResources.getBoolean(R.bool.config_enableNotificationShadeDrag)).thenReturn(true);
-        when(mView.getContext()).thenReturn(getContext());
-        when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch);
-        when(mView.findViewById(R.id.notification_stack_scroller))
-                .thenReturn(mNotificationStackScrollLayout);
         when(mNotificationStackScrollLayout.getHeight()).thenReturn(1000);
         when(mNotificationStackScrollLayout.getHeadsUpCallback()).thenReturn(mHeadsUpCallback);
-        when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
-        when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
-        when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
-        when(mView.findViewById(R.id.big_clock_container)).thenReturn(mBigClockContainer);
-        when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
-        mFlingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(mDisplayMetrics);
-
-        doAnswer((Answer<Void>) invocation -> {
-            mTouchHandler = invocation.getArgument(0);
-            return null;
-        }).when(mView).setOnTouchListener(any(PanelViewController.TouchHandler.class));
-
+        when(mHeadsUpCallback.getContext()).thenReturn(mContext);
+        mDependency.injectTestDependency(StatusBarStateController.class,
+                mStatusBarStateController);
+        mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mUpdateMonitor);
+        mDependency.injectMockDependency(ConfigurationController.class);
+        mDependency.injectMockDependency(ZenModeController.class);
         NotificationWakeUpCoordinator coordinator =
                 new NotificationWakeUpCoordinator(
                         mock(HeadsUpManagerPhone.class),
@@ -215,26 +143,18 @@
                 mock(NotificationRoundnessManager.class),
                 mStatusBarStateController,
                 new FalsingManagerFake());
-        mNotificationPanelViewController = new NotificationPanelViewController(mView,
-                mInjectionInflationController,
-                coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
-                mFalsingManager, mPluginManager, mShadeController,
-                mNotificationLockscreenUserManager, mNotificationEntryManager,
-                mKeyguardStateController, mStatusBarStateController, mDozeLog,
-                mDozeParameters, mCommandQueue, mVibratorHelper,
-                mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
-                mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
-                mFlingAnimationUtilsBuilder);
-        mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
-                mNotificationShelf, mNotificationAreaController, mScrimController);
-        mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
-        mNotificationPanelViewController.setBar(mPanelBar);
+        mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler,
+                mKeyguardBypassController, mStatusBarStateController);
+        mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
+        mNotificationPanelView.setBar(mPanelBar);
+
+        when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
+        when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
     }
 
     @Test
     public void testSetDozing_notifiesNsslAndStateController() {
-        mNotificationPanelViewController.setDozing(true /* dozing */, true /* animate */,
-                null /* touch */);
+        mNotificationPanelView.setDozing(true /* dozing */, true /* animate */, null /* touch */);
         InOrder inOrder = inOrder(mNotificationStackScrollLayout, mStatusBarStateController);
         inOrder.verify(mNotificationStackScrollLayout).setDozing(eq(true), eq(true), eq(null));
         inOrder.verify(mStatusBarStateController).setDozeAmount(eq(1f), eq(true));
@@ -242,63 +162,103 @@
 
     @Test
     public void testSetExpandedHeight() {
-        mNotificationPanelViewController.setExpandedHeight(200);
-        assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
+        mNotificationPanelView.setExpandedHeight(200);
+        assertThat((int) mNotificationPanelView.getExpandedHeight()).isEqualTo(200);
     }
 
     @Test
     public void testAffordanceLaunchingListener() {
         Consumer<Boolean> listener = spy((showing) -> { });
-        mNotificationPanelViewController.setExpandedFraction(1f);
-        mNotificationPanelViewController.setLaunchAffordanceListener(listener);
-        mNotificationPanelViewController.launchCamera(false /* animate */,
+        mNotificationPanelView.setExpandedFraction(1f);
+        mNotificationPanelView.setLaunchAffordanceListener(listener);
+        mNotificationPanelView.launchCamera(false /* animate */,
                 StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
         verify(listener).accept(eq(true));
 
-        mNotificationPanelViewController.onAffordanceLaunchEnded();
+        mNotificationPanelView.onAffordanceLaunchEnded();
         verify(listener).accept(eq(false));
     }
 
     @Test
     public void testOnTouchEvent_expansionCanBeBlocked() {
-        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+        mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
                 0 /* metaState */));
-        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+        mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
                 0 /* metaState */));
-        assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
-        assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse();
+        assertThat((int) mNotificationPanelView.getExpandedHeight()).isEqualTo(200);
+        assertThat(mNotificationPanelView.isTrackingBlocked()).isFalse();
 
-        mNotificationPanelViewController.blockExpansionForCurrentTouch();
-        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+        mNotificationPanelView.blockExpansionForCurrentTouch();
+        mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */,
                 0 /* metaState */));
         // Expansion should not have changed because it was blocked
-        assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
-        assertThat(mNotificationPanelViewController.isTrackingBlocked()).isTrue();
+        assertThat((int) mNotificationPanelView.getExpandedHeight()).isEqualTo(200);
+        assertThat(mNotificationPanelView.isTrackingBlocked()).isTrue();
 
-        onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+        mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
                 0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
                 0 /* metaState */));
-        assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse();
+        assertThat(mNotificationPanelView.isTrackingBlocked()).isFalse();
     }
 
     @Test
     public void testKeyguardStatusBarVisibility_hiddenForBypass() {
         when(mUpdateMonitor.shouldListenForFace()).thenReturn(true);
-        mNotificationPanelViewController.mKeyguardUpdateCallback.onBiometricRunningStateChanged(
-                true, BiometricSourceType.FACE);
+        mNotificationPanelView.mKeyguardUpdateCallback.onBiometricRunningStateChanged(true,
+                BiometricSourceType.FACE);
         verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
 
         when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
-        mNotificationPanelViewController.mKeyguardUpdateCallback.onFinishedGoingToSleep(0);
-        mNotificationPanelViewController.mKeyguardUpdateCallback.onBiometricRunningStateChanged(
-                true, BiometricSourceType.FACE);
+        mNotificationPanelView.mKeyguardUpdateCallback.onFinishedGoingToSleep(0);
+        mNotificationPanelView.mKeyguardUpdateCallback.onBiometricRunningStateChanged(true,
+                BiometricSourceType.FACE);
         verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
     }
 
-    private void onTouchEvent(MotionEvent ev) {
-        mTouchHandler.onTouch(mView, ev);
+    private class TestableNotificationPanelView extends NotificationPanelView {
+        TestableNotificationPanelView(NotificationWakeUpCoordinator coordinator,
+                PulseExpansionHandler expansionHandler,
+                KeyguardBypassController bypassController,
+                SysuiStatusBarStateController statusBarStateController) {
+            super(
+                    NotificationPanelViewTest.this.mContext,
+                    null,
+                    new InjectionInflationController(
+                            SystemUIFactory.getInstance().getRootComponent()),
+                    coordinator,
+                    expansionHandler,
+                    mock(DynamicPrivacyController.class),
+                    bypassController,
+                    mFalsingManager,
+                    mock(PluginManager.class),
+                    mock(ShadeController.class),
+                    mock(NotificationLockscreenUserManager.class),
+                    new NotificationEntryManager(
+                            mock(NotifLog.class),
+                            mock(NotificationGroupManager.class),
+                            mock(NotificationRankingManager.class),
+                            mock(NotificationEntryManager.KeyguardEnvironment.class)),
+                    mock(KeyguardStateController.class),
+                    statusBarStateController,
+                    mock(DozeLog.class),
+                    mDozeParameters,
+                    new CommandQueue(NotificationPanelViewTest.this.mContext));
+            mNotificationStackScroller = mNotificationStackScrollLayout;
+            mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView;
+            mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar;
+            mKeyguardBottomArea = NotificationPanelViewTest.this.mKeyguardBottomArea;
+            mBigClockContainer = NotificationPanelViewTest.this.mBigClockContainer;
+            mQsFrame = NotificationPanelViewTest.this.mQsFrame;
+            mAffordanceHelper = NotificationPanelViewTest.this.mAffordanceHelper;
+            initDependencies(NotificationPanelViewTest.this.mStatusBar,
+                    NotificationPanelViewTest.this.mGroupManager,
+                    NotificationPanelViewTest.this.mNotificationShelf,
+                    NotificationPanelViewTest.this.mHeadsUpManager,
+                    NotificationPanelViewTest.this.mNotificationAreaController,
+                    NotificationPanelViewTest.this.mScrimController);
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 5b5eaad..b27e84a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -74,7 +74,7 @@
     @Mock
     private ViewGroup mContainer;
     @Mock
-    private NotificationPanelViewController mNotificationPanelView;
+    private NotificationPanelView mNotificationPanelView;
     @Mock
     private BiometricUnlockController mBiometrucUnlockController;
     @Mock
@@ -281,12 +281,12 @@
 
         @Override
         public void registerStatusBar(StatusBar statusBar, ViewGroup container,
-                NotificationPanelViewController notificationPanelViewController,
+                NotificationPanelView notificationPanelView,
                 BiometricUnlockController fingerprintUnlockController,
                 DismissCallbackRegistry dismissCallbackRegistry,
                 ViewGroup lockIconContainer, View notificationContainer,
                 KeyguardBypassController bypassController, FalsingManager falsingManager) {
-            super.registerStatusBar(statusBar, container, notificationPanelViewController,
+            super.registerStatusBar(statusBar, container, notificationPanelView,
                     fingerprintUnlockController, dismissCallbackRegistry, lockIconContainer,
                     notificationContainer, bypassController, falsingManager);
             mBouncer = StatusBarKeyguardViewManagerTest.this.mBouncer;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index fea4b8b..86b2a44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -64,6 +64,7 @@
 import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -123,6 +124,10 @@
     private Intent mContentIntentInner;
     @Mock
     private NotificationActivityStarter mNotificationActivityStarter;
+    @Mock
+    private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
+    @Mock
+    private NotificationPanelView mNotificationPanelView;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
 
     private NotificationTestHelper mNotificationTestHelper;
@@ -162,6 +167,8 @@
         mActiveNotifications.add(mBubbleNotificationRow.getEntry());
         when(mEntryManager.getVisibleNotifications()).thenReturn(mActiveNotifications);
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
+        when(mSuperStatusBarViewFactory.getNotificationPanelView())
+                .thenReturn(mNotificationPanelView);
 
         mNotificationActivityStarter = (new StatusBarNotificationActivityStarter.Builder(
                 getContext(), mock(CommandQueue.class), () -> mAssistManager,
@@ -175,9 +182,9 @@
                 mKeyguardStateController,
                 mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
                 mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor,
-                mActivityIntentHelper, mBubbleController, mShadeController))
+                mActivityIntentHelper, mBubbleController, mShadeController,
+                mSuperStatusBarViewFactory))
                 .setStatusBar(mStatusBar)
-                .setNotificationPanelViewController(mock(NotificationPanelViewController.class))
                 .setNotificationPresenter(mock(NotificationPresenter.class))
                 .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))
         .build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 5ac7bfb..1296a97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -108,7 +108,7 @@
         StatusBarWindowView statusBarWindowView = mock(StatusBarWindowView.class);
         when(statusBarWindowView.getResources()).thenReturn(mContext.getResources());
         mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(mContext,
-                mock(NotificationPanelViewController.class), mock(HeadsUpManagerPhone.class),
+                mock(NotificationPanelView.class), mock(HeadsUpManagerPhone.class),
                 statusBarWindowView, mock(NotificationListContainerViewGroup.class),
                 mock(DozeScrimController.class), mock(ScrimController.class),
                 mock(ActivityLaunchAnimator.class), mock(DynamicPrivacyController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 7e485f4..1cdba47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -124,7 +124,6 @@
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -173,7 +172,6 @@
     @Mock private KeyguardIndicationController mKeyguardIndicationController;
     @Mock private NotificationStackScrollLayout mStackScroller;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
-    @Mock private NotificationPanelViewController mNotificationPanelViewController;
     @Mock private NotificationPanelView mNotificationPanelView;
     @Mock private IStatusBarService mBarService;
     @Mock private IDreamManager mDreamManager;
@@ -287,7 +285,6 @@
         mContext.setTheme(R.style.Theme_SystemUI_Light);
 
         when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
-        when(mNotificationPanelViewController.getView()).thenReturn(mNotificationPanelView);
         when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
         when(powerManagerService.isInteractive()).thenReturn(true);
         when(mStackScroller.getActivatedChild()).thenReturn(null);
@@ -419,7 +416,7 @@
                 mLockIconContainer);
 
         when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class),
-                any(NotificationPanelViewController.class), any(BiometricUnlockController.class),
+                any(NotificationPanelView.class), any(BiometricUnlockController.class),
                 any(ViewGroup.class), any(ViewGroup.class), any(KeyguardBypassController.class)))
                 .thenReturn(mStatusBarKeyguardViewManager);
 
@@ -429,7 +426,7 @@
         // TODO: we should be able to call mStatusBar.start() and have all the below values
         // initialized automatically.
         mStatusBar.mStatusBarWindow = mStatusBarWindowView;
-        mStatusBar.mNotificationPanelViewController = mNotificationPanelViewController;
+        mStatusBar.mNotificationPanel = mNotificationPanelView;
         mStatusBar.mDozeScrimController = mDozeScrimController;
         mStatusBar.mNotificationIconAreaController = mNotificationIconAreaController;
         mStatusBar.mPresenter = mNotificationPresenter;
@@ -734,20 +731,20 @@
         when(mCommandQueue.panelsEnabled()).thenReturn(false);
         mStatusBar.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
                 StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
-        verify(mNotificationPanelViewController).setQsExpansionEnabled(false);
+        verify(mNotificationPanelView).setQsExpansionEnabled(false);
         mStatusBar.animateExpandNotificationsPanel();
-        verify(mNotificationPanelViewController, never()).expand(anyBoolean());
+        verify(mNotificationPanelView, never()).expand(anyBoolean());
         mStatusBar.animateExpandSettingsPanel(null);
-        verify(mNotificationPanelViewController, never()).expand(anyBoolean());
+        verify(mNotificationPanelView, never()).expand(anyBoolean());
 
         when(mCommandQueue.panelsEnabled()).thenReturn(true);
         mStatusBar.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
                 StatusBarManager.DISABLE2_NONE, false);
-        verify(mNotificationPanelViewController).setQsExpansionEnabled(true);
+        verify(mNotificationPanelView).setQsExpansionEnabled(true);
         mStatusBar.animateExpandNotificationsPanel();
-        verify(mNotificationPanelViewController).expandWithoutQs();
+        verify(mNotificationPanelView).expandWithoutQs();
         mStatusBar.animateExpandSettingsPanel(null);
-        verify(mNotificationPanelViewController).expandWithQs();
+        verify(mNotificationPanelView).expandWithQs();
     }
 
     @Test
@@ -837,12 +834,12 @@
         when(mDozeServiceHost.getDozingRequested()).thenReturn(true);
         mStatusBar.updateIsKeyguard();
         // TODO: mNotificationPanelView.expand(false) gets called twice. Should be once.
-        verify(mNotificationPanelViewController, times(2)).expand(eq(false));
-        clearInvocations(mNotificationPanelViewController);
+        verify(mNotificationPanelView, times(2)).expand(eq(false));
+        clearInvocations(mNotificationPanelView);
 
         mStatusBar.mWakefulnessObserver.onStartedWakingUp();
         verify(mDozeServiceHost).stopDozing();
-        verify(mNotificationPanelViewController).expand(eq(false));
+        verify(mNotificationPanelView).expand(eq(false));
     }
 
     @Test
@@ -851,11 +848,11 @@
         when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
         when(mDozeServiceHost.getDozingRequested()).thenReturn(true);
         mStatusBar.updateIsKeyguard();
-        clearInvocations(mNotificationPanelViewController);
+        clearInvocations(mNotificationPanelView);
 
         mStatusBar.setBouncerShowing(true);
         mStatusBar.mWakefulnessObserver.onStartedWakingUp();
-        verify(mNotificationPanelViewController, never()).expand(anyBoolean());
+        verify(mNotificationPanelView, never()).expand(anyBoolean());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
index f9848f3..9f899ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -27,7 +26,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.classifier.FalsingManagerFake;
@@ -42,7 +40,6 @@
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.InjectionInflationController;
@@ -77,16 +74,12 @@
     @Mock private DozeLog mDozeLog;
     @Mock private DozeParameters mDozeParameters;
     @Mock private DockManager mDockManager;
-    @Mock private NotificationPanelViewController mNotificationPanelViewController;
-    @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mView = spy(new StatusBarWindowView(getContext(), null));
-        when(mView.findViewById(R.id.notification_stack_scroller))
-                .thenReturn(mNotificationStackScrollLayout);
+        mView = new StatusBarWindowView(getContext(), null);
         when(mStatusBarStateController.isDozing()).thenReturn(false);
         mDependency.injectTestDependency(ShadeController.class, mShadeController);
 
@@ -111,8 +104,7 @@
                 new CommandQueue(mContext),
                 mShadeController,
                 mDockManager,
-                mView,
-                mNotificationPanelViewController);
+                mView);
         mController.setupExpandedStatusBar();
         mController.setService(mStatusBar);
         mController.setDragDownHelper(mDragDownHelper);
diff --git a/packages/Tethering/jarjar-rules.txt b/packages/Tethering/jarjar-rules.txt
index dd9eab7..d93531b 100644
--- a/packages/Tethering/jarjar-rules.txt
+++ b/packages/Tethering/jarjar-rules.txt
@@ -13,3 +13,5 @@
 rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1
 
 rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
+
+rule android.net.shared.Inet4AddressUtils* com.android.networkstack.tethering.shared.Inet4AddressUtils@1
diff --git a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
index 1fe2328..d6bc063 100644
--- a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
+++ b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -20,11 +20,11 @@
 
 import android.annotation.NonNull;
 import android.net.LinkAddress;
-
-import com.google.android.collect.Sets;
+import android.util.ArraySet;
 
 import java.net.Inet4Address;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Set;
 
 /**
@@ -68,7 +68,7 @@
      * but it must always be set explicitly.
      */
     public DhcpServingParamsParcelExt setDefaultRouters(@NonNull Inet4Address... defaultRouters) {
-        return setDefaultRouters(Sets.newArraySet(defaultRouters));
+        return setDefaultRouters(newArraySet(defaultRouters));
     }
 
     /**
@@ -96,7 +96,7 @@
      * <p>This may be an empty list of servers, but it must always be set explicitly.
      */
     public DhcpServingParamsParcelExt setDnsServers(@NonNull Inet4Address... dnsServers) {
-        return setDnsServers(Sets.newArraySet(dnsServers));
+        return setDnsServers(newArraySet(dnsServers));
     }
 
     /**
@@ -126,7 +126,7 @@
      * and do not need to be set here.
      */
     public DhcpServingParamsParcelExt setExcludedAddrs(@NonNull Inet4Address... excludedAddrs) {
-        return setExcludedAddrs(Sets.newArraySet(excludedAddrs));
+        return setExcludedAddrs(newArraySet(excludedAddrs));
     }
 
     /**
@@ -169,4 +169,10 @@
         }
         return res;
     }
+
+    private static ArraySet<Inet4Address> newArraySet(Inet4Address... addrs) {
+        ArraySet<Inet4Address> addrSet = new ArraySet<>(addrs.length);
+        Collections.addAll(addrSet, addrs);
+        return addrSet;
+    }
 }
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 8fde520..abfb33c 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -17,10 +17,12 @@
 package android.net.ip;
 
 import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.util.NetworkConstants.FF;
 import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
 import static android.net.util.NetworkConstants.asByte;
+import static android.net.util.TetheringMessageBase.BASE_IPSERVER;
 
 import android.net.ConnectivityManager;
 import android.net.INetd;
@@ -46,11 +48,9 @@
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Log;
-import android.util.Slog;
 import android.util.SparseArray;
 
 import com.android.internal.util.MessageUtils;
-import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 
@@ -153,27 +153,26 @@
                 DhcpServerCallbacks cb);
     }
 
-    private static final int BASE_IFACE              = Protocol.BASE_TETHERING + 100;
     // request from the user that it wants to tether
-    public static final int CMD_TETHER_REQUESTED            = BASE_IFACE + 2;
+    public static final int CMD_TETHER_REQUESTED            = BASE_IPSERVER + 1;
     // request from the user that it wants to untether
-    public static final int CMD_TETHER_UNREQUESTED          = BASE_IFACE + 3;
+    public static final int CMD_TETHER_UNREQUESTED          = BASE_IPSERVER + 2;
     // notification that this interface is down
-    public static final int CMD_INTERFACE_DOWN              = BASE_IFACE + 4;
+    public static final int CMD_INTERFACE_DOWN              = BASE_IPSERVER + 3;
     // notification from the master SM that it had trouble enabling IP Forwarding
-    public static final int CMD_IP_FORWARDING_ENABLE_ERROR  = BASE_IFACE + 7;
+    public static final int CMD_IP_FORWARDING_ENABLE_ERROR  = BASE_IPSERVER + 4;
     // notification from the master SM that it had trouble disabling IP Forwarding
-    public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8;
+    public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IPSERVER + 5;
     // notification from the master SM that it had trouble starting tethering
-    public static final int CMD_START_TETHERING_ERROR       = BASE_IFACE + 9;
+    public static final int CMD_START_TETHERING_ERROR       = BASE_IPSERVER + 6;
     // notification from the master SM that it had trouble stopping tethering
-    public static final int CMD_STOP_TETHERING_ERROR        = BASE_IFACE + 10;
+    public static final int CMD_STOP_TETHERING_ERROR        = BASE_IPSERVER + 7;
     // notification from the master SM that it had trouble setting the DNS forwarders
-    public static final int CMD_SET_DNS_FORWARDERS_ERROR    = BASE_IFACE + 11;
+    public static final int CMD_SET_DNS_FORWARDERS_ERROR    = BASE_IPSERVER + 8;
     // the upstream connection has changed
-    public static final int CMD_TETHER_CONNECTION_CHANGED   = BASE_IFACE + 12;
+    public static final int CMD_TETHER_CONNECTION_CHANGED   = BASE_IPSERVER + 9;
     // new IPv6 tethering parameters need to be processed
-    public static final int CMD_IPV6_TETHER_UPDATE          = BASE_IFACE + 13;
+    public static final int CMD_IPV6_TETHER_UPDATE          = BASE_IPSERVER + 10;
 
     private final State mInitialState;
     private final State mLocalHotspotState;
@@ -486,7 +485,9 @@
         }
 
         // Directly-connected route.
-        final RouteInfo route = new RouteInfo(linkAddr);
+        final IpPrefix ipv4Prefix = new IpPrefix(linkAddr.getAddress(),
+                linkAddr.getPrefixLength());
+        final RouteInfo route = new RouteInfo(ipv4Prefix, null, null, RTN_UNICAST);
         if (enabled) {
             mLinkProperties.addLinkAddress(linkAddr);
             mLinkProperties.addRoute(route);
@@ -1007,7 +1008,7 @@
             String ifname, HashSet<IpPrefix> prefixes) {
         final ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>();
         for (IpPrefix ipp : prefixes) {
-            localRoutes.add(new RouteInfo(ipp, null, ifname));
+            localRoutes.add(new RouteInfo(ipp, null, ifname, RTN_UNICAST));
         }
         return localRoutes;
     }
@@ -1019,7 +1020,7 @@
         try {
             return Inet6Address.getByAddress(null, dnsBytes, 0);
         } catch (UnknownHostException e) {
-            Slog.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix);
+            Log.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix);
             return null;
         }
     }
diff --git a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
index 4396cdb..bba61d7 100644
--- a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
+++ b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -22,14 +22,13 @@
 import static android.system.OsConstants.IPPROTO_ICMPV6;
 import static android.system.OsConstants.SOCK_RAW;
 import static android.system.OsConstants.SOL_SOCKET;
-import static android.system.OsConstants.SO_BINDTODEVICE;
 import static android.system.OsConstants.SO_SNDTIMEO;
 
 import android.net.IpPrefix;
 import android.net.LinkAddress;
-import android.net.NetworkUtils;
 import android.net.TrafficStats;
 import android.net.util.InterfaceParams;
+import android.net.util.SocketUtils;
 import android.net.util.TetheringUtils;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -39,8 +38,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.TrafficStatsConstants;
 
-import libcore.io.IoBridge;
-
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.net.Inet6Address;
@@ -612,8 +609,7 @@
             // Setting SNDTIMEO is purely for defensive purposes.
             Os.setsockoptTimeval(
                     mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(send_timout_ms));
-            Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mInterface.name);
-            NetworkUtils.protectFromVpn(mSocket);
+            SocketUtils.bindSocketToInterface(mSocket, mInterface.name);
             TetheringUtils.setupRaSocket(mSocket, mInterface.index);
         } catch (ErrnoException | IOException e) {
             Log.e(TAG, "Failed to create RA daemon socket: " + e);
@@ -628,7 +624,7 @@
     private void closeSocket() {
         if (mSocket != null) {
             try {
-                IoBridge.closeAndSignalBlockedThreads(mSocket);
+                SocketUtils.closeSocket(mSocket);
             } catch (IOException ignored) { }
         }
         mSocket = null;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java b/packages/Tethering/src/android/net/util/TetheringMessageBase.java
similarity index 61%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
copy to packages/Tethering/src/android/net/util/TetheringMessageBase.java
index 155a6d2..1b763ce 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
+++ b/packages/Tethering/src/android/net/util/TetheringMessageBase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 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.
@@ -13,18 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.net.util;
 
-package com.android.systemui.dagger.qualifiers;
+/**
+ * This class defines Message.what base addresses for various state machine.
+ */
+public class TetheringMessageBase {
+    public static final int BASE_MASTER   = 0;
+    public static final int BASE_IPSERVER = 100;
 
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Qualifier;
-
-@Qualifier
-@Documented
-@Retention(RUNTIME)
-public @interface DisplayId {
 }
diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java
index 2fb73ce..fa543bd 100644
--- a/packages/Tethering/src/android/net/util/TetheringUtils.java
+++ b/packages/Tethering/src/android/net/util/TetheringUtils.java
@@ -39,4 +39,11 @@
      */
     public static native void setupRaSocket(FileDescriptor fd, int ifIndex)
             throws SocketException;
+
+    /**
+     * Read s as an unsigned 16-bit integer.
+     */
+    public static int uint16(short s) {
+        return s & 0xffff;
+    }
 }
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
index ba5d08d..7e685fb 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -38,7 +38,6 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.net.util.SharedLog;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -48,7 +47,6 @@
 import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
 import android.util.ArraySet;
@@ -196,9 +194,9 @@
             // till upstream change to cellular.
             if (mUsingCellularAsUpstream) {
                 if (showProvisioningUi) {
-                    runUiTetherProvisioning(type, config.subId);
+                    runUiTetherProvisioning(type, config.activeDataSubId);
                 } else {
-                    runSilentTetherProvisioning(type, config.subId);
+                    runSilentTetherProvisioning(type, config.activeDataSubId);
                 }
                 mNeedReRunProvisioningUi = false;
             } else {
@@ -270,9 +268,9 @@
             if (mCellularPermitted.indexOfKey(downstream) < 0) {
                 if (mNeedReRunProvisioningUi) {
                     mNeedReRunProvisioningUi = false;
-                    runUiTetherProvisioning(downstream, config.subId);
+                    runUiTetherProvisioning(downstream, config.activeDataSubId);
                 } else {
-                    runSilentTetherProvisioning(downstream, config.subId);
+                    runSilentTetherProvisioning(downstream, config.activeDataSubId);
                 }
             }
         }
@@ -336,7 +334,8 @@
                 .getSystemService(Context.CARRIER_CONFIG_SERVICE);
         if (configManager == null) return null;
 
-        final PersistableBundle carrierConfig = configManager.getConfigForSubId(config.subId);
+        final PersistableBundle carrierConfig = configManager.getConfigForSubId(
+                config.activeDataSubId);
 
         if (CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig)) {
             return carrierConfig;
@@ -379,12 +378,9 @@
         intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
         intent.putExtra(EXTRA_SUBID, subId);
         intent.setComponent(TETHER_SERVICE);
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            mContext.startServiceAsUser(intent, UserHandle.CURRENT);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
+        // Only admin user can change tethering and SilentTetherProvisioning don't need to
+        // show UI, it is fine to always start setting's background service as system user.
+        mContext.startService(intent);
     }
 
     private void runUiTetherProvisioning(int type, int subId) {
@@ -407,12 +403,9 @@
         intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
         intent.putExtra(EXTRA_SUBID, subId);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
+        // Only launch entitlement UI for system user. Entitlement UI should not appear for other
+        // user because only admin user is allowed to change tethering.
+        mContext.startActivity(intent);
     }
 
     // Not needed to check if this don't run on the handler thread because it's private.
@@ -671,7 +664,7 @@
             receiver.send(cacheValue, null);
         } else {
             ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver);
-            runUiTetherProvisioning(downstream, config.subId, proxy);
+            runUiTetherProvisioning(downstream, config.activeDataSubId, proxy);
         }
     }
 }
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
index 9305414..66b9ade 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
@@ -29,6 +29,7 @@
 
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedList;
@@ -257,7 +258,7 @@
         final LinkProperties lp = new LinkProperties();
 
         final IpPrefix local48 = makeUniqueLocalPrefix(ulp, (short) 0, 48);
-        lp.addRoute(new RouteInfo(local48, null, null));
+        lp.addRoute(new RouteInfo(local48, null, null, RouteInfo.RTN_UNICAST));
 
         final IpPrefix local64 = makeUniqueLocalPrefix(ulp, subnetId, 64);
         // Because this is a locally-generated ULA, we don't have an upstream
@@ -273,7 +274,13 @@
         final byte[] bytes = Arrays.copyOf(in6addr, in6addr.length);
         bytes[7] = (byte) (subnetId >> 8);
         bytes[8] = (byte) subnetId;
-        return new IpPrefix(bytes, prefixlen);
+        final InetAddress addr;
+        try {
+            addr = InetAddress.getByAddress(bytes);
+        } catch (UnknownHostException e) {
+            throw new IllegalStateException("Invalid address length: " + bytes.length, e);
+        }
+        return new IpPrefix(addr, prefixlen);
     }
 
     // Generates a Unique Locally-assigned Prefix:
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
index 16734d8..38fa91e 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
@@ -25,6 +25,7 @@
 
 import android.content.ContentResolver;
 import android.net.ITetheringStatsProvider;
+import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
@@ -33,7 +34,6 @@
 import android.net.netlink.ConntrackMessage;
 import android.net.netlink.NetlinkConstants;
 import android.net.netlink.NetlinkSocket;
-import android.net.util.IpUtils;
 import android.net.util.SharedLog;
 import android.os.Handler;
 import android.os.INetworkManagementService;
@@ -477,9 +477,10 @@
             if (!ri.hasGateway()) continue;
 
             final String gateway = ri.getGateway().getHostAddress();
-            if (ri.isIPv4Default()) {
+            final InetAddress address = ri.getDestination().getAddress();
+            if (ri.isDefaultRoute() && address instanceof Inet4Address) {
                 v4gateway = gateway;
-            } else if (ri.isIPv6Default()) {
+            } else if (ri.isDefaultRoute() && address instanceof Inet6Address) {
                 v6gateways.add(gateway);
             }
         }
@@ -547,7 +548,10 @@
 
     private static boolean shouldIgnoreDownstreamRoute(RouteInfo route) {
         // Ignore any link-local routes.
-        if (!route.getDestinationLinkAddress().isGlobalPreferred()) return true;
+        final IpPrefix destination = route.getDestination();
+        final LinkAddress linkAddr = new LinkAddress(destination.getAddress(),
+                destination.getPrefixLength());
+        if (!linkAddr.isGlobalPreferred()) return true;
 
         return false;
     }
@@ -588,7 +592,7 @@
             return;
         }
 
-        if (!IpUtils.isValidUdpOrTcpPort(srcPort)) {
+        if (!isValidUdpOrTcpPort(srcPort)) {
             mLog.e("Invalid src port: " + srcPort);
             return;
         }
@@ -599,7 +603,7 @@
             return;
         }
 
-        if (!IpUtils.isValidUdpOrTcpPort(dstPort)) {
+        if (!isValidUdpOrTcpPort(dstPort)) {
             mLog.e("Invalid dst port: " + dstPort);
             return;
         }
@@ -628,7 +632,7 @@
 
     private static Inet4Address parseIPv4Address(String addrString) {
         try {
-            final InetAddress ip = InetAddress.parseNumericAddress(addrString);
+            final InetAddress ip = InetAddresses.parseNumericAddress(addrString);
             // TODO: Consider other sanitization steps here, including perhaps:
             //           not eql to 0.0.0.0
             //           not within 169.254.0.0/16
@@ -668,4 +672,8 @@
             return 180;
         }
     }
+
+    private static boolean isValidUdpOrTcpPort(int port) {
+        return port > 0 && port < 65536;
+    }
 }
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 85164ed..4a8ef1f 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -16,7 +16,7 @@
 
 package com.android.server.connectivity.tethering;
 
-import static com.android.internal.util.BitUtils.uint16;
+import static android.net.util.TetheringUtils.uint16;
 
 import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
 import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index 0b5759b..5b26704 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -37,6 +37,7 @@
 import static android.net.ConnectivityManager.TETHER_ERROR_SERVICE_UNAVAIL;
 import static android.net.ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
 import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.util.TetheringMessageBase.BASE_MASTER;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
@@ -106,7 +107,6 @@
 import com.android.internal.notification.SystemNotificationChannels;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.MessageUtils;
-import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.networkstack.tethering.R;
@@ -120,6 +120,8 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
 
 /**
  *
@@ -185,10 +187,10 @@
     private final TetheringDependencies mDeps;
     private final EntitlementManager mEntitlementMgr;
     private final Handler mHandler;
-    private final PhoneStateListener mPhoneStateListener;
     private final INetd mNetd;
     private final NetdCallback mNetdCallback;
     private final UserRestrictionActionListener mTetheringRestriction;
+    private final ActiveDataSubIdListener mActiveDataSubIdListener;
     private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
     // All the usage of mTetheringEventCallback should run in the same thread.
     private ITetheringEventCallback mTetheringEventCallback = null;
@@ -252,26 +254,6 @@
                     mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
                 });
 
-        mPhoneStateListener = new PhoneStateListener(mLooper) {
-            @Override
-            public void onActiveDataSubscriptionIdChanged(int subId) {
-                mLog.log("OBSERVED active data subscription change, from " + mActiveDataSubId
-                        + " to " + subId);
-                if (subId == mActiveDataSubId) return;
-
-                mActiveDataSubId = subId;
-                updateConfiguration();
-                // To avoid launching unexpected provisioning checks, ignore re-provisioning when
-                // no CarrierConfig loaded yet. Assume reevaluateSimCardProvisioning() will be
-                // triggered again when CarrierConfig is loaded.
-                if (mEntitlementMgr.getCarrierConfig(mConfig) != null) {
-                    mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
-                } else {
-                    mLog.log("IGNORED reevaluate provisioning due to no carrier config loaded");
-                }
-            }
-        };
-
         mStateReceiver = new StateReceiver();
 
         mNetdCallback = new NetdCallback();
@@ -284,6 +266,8 @@
         final UserManager userManager = (UserManager) mContext.getSystemService(
                     Context.USER_SERVICE);
         mTetheringRestriction = new UserRestrictionActionListener(userManager, this);
+        final TetheringThreadExecutor executor = new TetheringThreadExecutor(mHandler);
+        mActiveDataSubIdListener = new ActiveDataSubIdListener(executor);
 
         // Load tethering configuration.
         updateConfiguration();
@@ -294,8 +278,8 @@
 
     private void startStateMachineUpdaters(Handler handler) {
         mCarrierConfigChange.startListening();
-        mContext.getSystemService(TelephonyManager.class).listen(
-                mPhoneStateListener, PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+        mContext.getSystemService(TelephonyManager.class).listen(mActiveDataSubIdListener,
+                PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(UsbManager.ACTION_USB_STATE);
@@ -314,6 +298,43 @@
 
     }
 
+    private class TetheringThreadExecutor implements Executor {
+        private final Handler mTetherHandler;
+        TetheringThreadExecutor(Handler handler) {
+            mTetherHandler = handler;
+        }
+        @Override
+        public void execute(Runnable command) {
+            if (!mTetherHandler.post(command)) {
+                throw new RejectedExecutionException(mTetherHandler + " is shutting down");
+            }
+        }
+    }
+
+    private class ActiveDataSubIdListener extends PhoneStateListener {
+        ActiveDataSubIdListener(Executor executor) {
+            super(executor);
+        }
+
+        @Override
+        public void onActiveDataSubscriptionIdChanged(int subId) {
+            mLog.log("OBSERVED active data subscription change, from " + mActiveDataSubId
+                    + " to " + subId);
+            if (subId == mActiveDataSubId) return;
+
+            mActiveDataSubId = subId;
+            updateConfiguration();
+            // To avoid launching unexpected provisioning checks, ignore re-provisioning
+            // when no CarrierConfig loaded yet. Assume reevaluateSimCardProvisioning()
+            // ill be triggered again when CarrierConfig is loaded.
+            if (mEntitlementMgr.getCarrierConfig(mConfig) != null) {
+                mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
+            } else {
+                mLog.log("IGNORED reevaluate provisioning, no carrier config loaded");
+            }
+        }
+    }
+
     private WifiManager getWifiManager() {
         return (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
     }
@@ -326,8 +347,7 @@
     }
 
     private void maybeDunSettingChanged() {
-        final boolean isDunRequired = TetheringConfiguration.checkDunRequired(
-                mContext, mActiveDataSubId);
+        final boolean isDunRequired = TetheringConfiguration.checkDunRequired(mContext);
         if (isDunRequired == mConfig.isDunRequired) return;
         updateConfiguration();
     }
@@ -1162,7 +1182,6 @@
     }
 
     class TetherMasterSM extends StateMachine {
-        private static final int BASE_MASTER                    = Protocol.BASE_TETHERING;
         // an interface SM has requested Tethering/Local Hotspot
         static final int EVENT_IFACE_SERVING_STATE_ACTIVE       = BASE_MASTER + 1;
         // an interface SM has unrequested Tethering/Local Hotspot
@@ -1179,7 +1198,6 @@
         static final int EVENT_IFACE_UPDATE_LINKPROPERTIES      = BASE_MASTER + 7;
         // Events from EntitlementManager to choose upstream again.
         static final int EVENT_UPSTREAM_PERMISSION_CHANGED      = BASE_MASTER + 8;
-
         private final State mInitialState;
         private final State mTetherModeAliveState;
 
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 0ab4d63..490614b 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -100,13 +100,13 @@
     public final String provisioningAppNoUi;
     public final int provisioningCheckPeriod;
 
-    public final int subId;
+    public final int activeDataSubId;
 
     public TetheringConfiguration(Context ctx, SharedLog log, int id) {
         final SharedLog configLog = log.forSubComponent("config");
 
-        subId = id;
-        Resources res = getResources(ctx, subId);
+        activeDataSubId = id;
+        Resources res = getResources(ctx, activeDataSubId);
 
         tetherableUsbRegexs = getResourceStringArray(res, config_tether_usb_regexs);
         // TODO: Evaluate deleting this altogether now that Wi-Fi always passes
@@ -116,7 +116,7 @@
         tetherableWifiP2pRegexs = getResourceStringArray(res, config_tether_wifi_p2p_regexs);
         tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs);
 
-        isDunRequired = checkDunRequired(ctx, subId);
+        isDunRequired = checkDunRequired(ctx);
 
         chooseUpstreamAutomatically = getResourceBoolean(res, config_tether_upstream_automatic);
         preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
@@ -166,8 +166,8 @@
 
     /** Does the dumping.*/
     public void dump(PrintWriter pw) {
-        pw.print("subId: ");
-        pw.println(subId);
+        pw.print("activeDataSubId: ");
+        pw.println(activeDataSubId);
 
         dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs);
         dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs);
@@ -196,7 +196,7 @@
     /** Returns the string representation of this object.*/
     public String toString() {
         final StringJoiner sj = new StringJoiner(" ");
-        sj.add(String.format("subId:%d", subId));
+        sj.add(String.format("activeDataSubId:%d", activeDataSubId));
         sj.add(String.format("tetherableUsbRegexs:%s", makeString(tetherableUsbRegexs)));
         sj.add(String.format("tetherableWifiRegexs:%s", makeString(tetherableWifiRegexs)));
         sj.add(String.format("tetherableWifiP2pRegexs:%s", makeString(tetherableWifiP2pRegexs)));
@@ -250,9 +250,11 @@
     }
 
     /** Check whether dun is required. */
-    public static boolean checkDunRequired(Context ctx, int id) {
+    public static boolean checkDunRequired(Context ctx) {
         final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
-        return (tm != null) ? tm.isTetheringApnRequired(id) : false;
+        // TelephonyManager would uses the active data subscription, which should be the one used
+        // by tethering.
+        return (tm != null) ? tm.isTetheringApnRequired() : false;
     }
 
     private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) {
@@ -391,7 +393,7 @@
      */
     public TetheringConfigurationParcel toStableParcelable() {
         final TetheringConfigurationParcel parcel = new TetheringConfigurationParcel();
-        parcel.subId = subId;
+        parcel.subId = activeDataSubId;
         parcel.tetherableUsbRegexs = tetherableUsbRegexs;
         parcel.tetherableWifiRegexs = tetherableWifiRegexs;
         parcel.tetherableBluetoothRegexs = tetherableBluetoothRegexs;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
index 1d59986..775484e 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
@@ -21,6 +21,7 @@
 import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
+import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
 
 import android.app.Service;
 import android.content.Context;
@@ -340,7 +341,10 @@
 
                                 service.makeDhcpServer(ifName, params, cb);
                             } catch (RemoteException e) {
-                                e.rethrowFromSystemServer();
+                                Log.e(TAG, "Fail to make dhcp server");
+                                try {
+                                    cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null);
+                                } catch (RemoteException re) { }
                             }
                         }
                     };
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index dc38c49a..22150f6 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -328,13 +328,6 @@
                     network, newNc));
         }
 
-        // Log changes in upstream network signal strength, if available.
-        if (network.equals(mTetheringUpstreamNetwork) && newNc.hasSignalStrength()) {
-            final int newSignal = newNc.getSignalStrength();
-            final String prevSignal = getSignalStrength(prev.networkCapabilities);
-            mLog.logf("upstream network signal strength: %s -> %s", prevSignal, newSignal);
-        }
-
         mNetworkMap.put(network, new UpstreamNetworkState(
                 prev.linkProperties, newNc, network));
         // TODO: If sufficient information is available to select a more
@@ -557,11 +550,6 @@
         return prefixSet;
     }
 
-    private static String getSignalStrength(NetworkCapabilities nc) {
-        if (nc == null || !nc.hasSignalStrength()) return "unknown";
-        return Integer.toString(nc.getSignalStrength());
-    }
-
     private static boolean isCellular(UpstreamNetworkState ns) {
         return (ns != null) && isCellular(ns.networkCapabilities);
     }
diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp
index 81a0548..53782fed 100644
--- a/packages/Tethering/tests/unit/Android.bp
+++ b/packages/Tethering/tests/unit/Android.bp
@@ -20,7 +20,11 @@
     srcs: [
         "src/**/*.java",
     ],
-    test_suites: ["device-tests"],
+    test_suites: [
+        "device-tests",
+        "mts",
+    ],
+    compile_multilib: "both",
     static_libs: [
         "androidx.test.rules",
         "frameworks-base-testutils",
diff --git a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
index e01ac7f..e8add98 100644
--- a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
@@ -18,8 +18,6 @@
 
 import static android.net.InetAddresses.parseNumericAddress;
 
-import static com.google.android.collect.Sets.newHashSet;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -34,6 +32,8 @@
 import org.junit.runner.RunWith;
 
 import java.net.Inet4Address;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -47,9 +47,10 @@
     private static final int TEST_LEASE_TIME_SECS = 120;
     private static final int TEST_MTU = 1000;
     private static final Set<Inet4Address> TEST_ADDRESS_SET =
-            newHashSet(inet4Addr("192.168.1.123"), inet4Addr("192.168.1.124"));
+            new HashSet<Inet4Address>(Arrays.asList(
+            new Inet4Address[] {inet4Addr("192.168.1.123"), inet4Addr("192.168.1.124")}));
     private static final Set<Integer> TEST_ADDRESS_SET_PARCELED =
-            newHashSet(0xc0a8017b, 0xc0a8017c);
+            new HashSet<Integer>(Arrays.asList(new Integer[] {0xc0a8017b, 0xc0a8017c}));
 
     private DhcpServingParamsParcelExt mParcel;
 
diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 4358cd6..fd2f708 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -510,8 +510,10 @@
         }
         assertNotNull("missing IPv4 address", addr4);
 
+        final IpPrefix destination = new IpPrefix(addr4.getAddress(), addr4.getPrefixLength());
         // Assert the presence of the associated directly connected route.
-        final RouteInfo directlyConnected = new RouteInfo(addr4, null, lp.getInterfaceName());
+        final RouteInfo directlyConnected = new RouteInfo(destination, null, lp.getInterfaceName(),
+                RouteInfo.RTN_UNICAST);
         assertTrue("missing directly connected route: '" + directlyConnected.toString() + "'",
                    lp.getRoutes().contains(directlyConnected));
     }
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
index 8574f54..7886ca6 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -21,6 +21,7 @@
 import static android.net.NetworkStats.STATS_PER_UID;
 import static android.net.NetworkStats.TAG_NONE;
 import static android.net.NetworkStats.UID_ALL;
+import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.TrafficStats.UID_TETHERING;
 import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
 
@@ -269,7 +270,7 @@
         final String ipv4Addr = "192.0.2.5";
         final String linkAddr = ipv4Addr + "/24";
         lp.addLinkAddress(new LinkAddress(linkAddr));
-        lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24")));
+        lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, null, RTN_UNICAST));
         offload.setUpstreamLinkProperties(lp);
         // IPv4 prefixes and addresses on the upstream are simply left as whole
         // prefixes (already passed in from UpstreamNetworkMonitor code). If a
@@ -285,7 +286,7 @@
         inOrder.verifyNoMoreInteractions();
 
         final String ipv4Gateway = "192.0.2.1";
-        lp.addRoute(new RouteInfo(InetAddress.getByName(ipv4Gateway)));
+        lp.addRoute(new RouteInfo(null, InetAddress.getByName(ipv4Gateway), null, RTN_UNICAST));
         offload.setUpstreamLinkProperties(lp);
         // No change in local addresses means no call to setLocalPrefixes().
         inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
@@ -296,7 +297,7 @@
         inOrder.verifyNoMoreInteractions();
 
         final String ipv6Gw1 = "fe80::cafe";
-        lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw1)));
+        lp.addRoute(new RouteInfo(null, InetAddress.getByName(ipv6Gw1), null, RTN_UNICAST));
         offload.setUpstreamLinkProperties(lp);
         // No change in local addresses means no call to setLocalPrefixes().
         inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
@@ -310,7 +311,7 @@
         inOrder.verifyNoMoreInteractions();
 
         final String ipv6Gw2 = "fe80::d00d";
-        lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw2)));
+        lp.addRoute(new RouteInfo(null, InetAddress.getByName(ipv6Gw2), null, RTN_UNICAST));
         offload.setUpstreamLinkProperties(lp);
         // No change in local addresses means no call to setLocalPrefixes().
         inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
@@ -327,8 +328,10 @@
         final LinkProperties stacked = new LinkProperties();
         stacked.setInterfaceName("stacked");
         stacked.addLinkAddress(new LinkAddress("192.0.2.129/25"));
-        stacked.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
-        stacked.addRoute(new RouteInfo(InetAddress.getByName("fe80::bad:f00")));
+        stacked.addRoute(new RouteInfo(null, InetAddress.getByName("192.0.2.254"), null,
+                  RTN_UNICAST));
+        stacked.addRoute(new RouteInfo(null, InetAddress.getByName("fe80::bad:f00"), null,
+                  RTN_UNICAST));
         assertTrue(lp.addStackedLink(stacked));
         offload.setUpstreamLinkProperties(lp);
         // No change in local addresses means no call to setLocalPrefixes().
@@ -348,7 +351,7 @@
         // removed from "local prefixes" and /128s added for the upstream IPv6
         // addresses.  This is not yet implemented, and for now we simply
         // expect to see these /128s.
-        lp.addRoute(new RouteInfo(new IpPrefix("2001:db8::/64")));
+        lp.addRoute(new RouteInfo(new IpPrefix("2001:db8::/64"), null, null, RTN_UNICAST));
         // "2001:db8::/64" plus "assigned" ASCII in hex
         lp.addLinkAddress(new LinkAddress("2001:db8::6173:7369:676e:6564/64"));
         // "2001:db8::/64" plus "random" ASCII in hex
@@ -574,13 +577,15 @@
         final LinkProperties usbLinkProperties = new LinkProperties();
         usbLinkProperties.setInterfaceName(RNDIS0);
         usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24"));
-        usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX)));
+        usbLinkProperties.addRoute(
+                new RouteInfo(new IpPrefix(USB_PREFIX), null, null, RTN_UNICAST));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
         inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, USB_PREFIX);
         inOrder.verifyNoMoreInteractions();
 
         // [2] Routes for IPv6 link-local prefixes should never be added.
-        usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL)));
+        usbLinkProperties.addRoute(
+                new RouteInfo(new IpPrefix(IPV6_LINKLOCAL), null, null, RTN_UNICAST));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
         inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString());
         inOrder.verifyNoMoreInteractions();
@@ -588,7 +593,8 @@
         // [3] Add an IPv6 prefix for good measure. Only new offload-able
         // prefixes should be passed to the HAL.
         usbLinkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
-        usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX)));
+        usbLinkProperties.addRoute(
+                new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, null, RTN_UNICAST));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
         inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
         inOrder.verifyNoMoreInteractions();
@@ -601,8 +607,10 @@
 
         // [5] Differences in local routes are converted into addDownstream()
         // and removeDownstream() invocations accordingly.
-        usbLinkProperties.removeRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, RNDIS0));
-        usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DISCARD_PREFIX)));
+        usbLinkProperties.removeRoute(
+                new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, RNDIS0, RTN_UNICAST));
+        usbLinkProperties.addRoute(
+                new RouteInfo(new IpPrefix(IPV6_DISCARD_PREFIX), null, null, RTN_UNICAST));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
         inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
         inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX);
@@ -680,19 +688,23 @@
         final LinkProperties usbLinkProperties = new LinkProperties();
         usbLinkProperties.setInterfaceName(RNDIS0);
         usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24"));
-        usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX)));
+        usbLinkProperties.addRoute(
+                new RouteInfo(new IpPrefix(USB_PREFIX), null, null, RTN_UNICAST));
         offload.notifyDownstreamLinkProperties(usbLinkProperties);
 
         final LinkProperties wifiLinkProperties = new LinkProperties();
         wifiLinkProperties.setInterfaceName(WLAN0);
         wifiLinkProperties.addLinkAddress(new LinkAddress("192.168.43.1/24"));
-        wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix(WIFI_PREFIX)));
-        wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL)));
+        wifiLinkProperties.addRoute(
+                new RouteInfo(new IpPrefix(WIFI_PREFIX), null, null, RTN_UNICAST));
+        wifiLinkProperties.addRoute(
+                new RouteInfo(new IpPrefix(IPV6_LINKLOCAL), null, null, RTN_UNICAST));
         // Use a benchmark prefix (RFC 5180 + erratum), since the documentation
         // prefix is included in the excluded prefix list.
         wifiLinkProperties.addLinkAddress(new LinkAddress("2001:2::1/64"));
         wifiLinkProperties.addLinkAddress(new LinkAddress("2001:2::2/64"));
-        wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix("2001:2::/64")));
+        wifiLinkProperties.addRoute(
+                new RouteInfo(new IpPrefix("2001:2::/64"), null, null, RTN_UNICAST));
         offload.notifyDownstreamLinkProperties(wifiLinkProperties);
 
         offload.removeDownstreamInterface(RNDIS0);
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
index 30bff35..7799da4 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -34,7 +34,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.when;
 
 import android.content.ContentResolver;
@@ -145,7 +144,7 @@
 
     @Test
     public void testDunFromTelephonyManagerMeansDun() {
-        when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(true);
+        when(mTelephonyManager.isTetheringApnRequired()).thenReturn(true);
 
         final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI);
         final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration(
@@ -169,7 +168,7 @@
 
     @Test
     public void testDunNotRequiredFromTelephonyManagerMeansNoDun() {
-        when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false);
+        when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
         final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI);
         final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration(
@@ -212,7 +211,7 @@
     @Test
     public void testNoDefinedUpstreamTypesAddsEthernet() {
         when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[]{});
-        when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false);
+        when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
         final TetheringConfiguration cfg = new TetheringConfiguration(
                 mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
@@ -235,7 +234,7 @@
     public void testDefinedUpstreamTypesSansEthernetAddsEthernet() {
         when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
                 new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI});
-        when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false);
+        when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
         final TetheringConfiguration cfg = new TetheringConfiguration(
                 mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
@@ -253,7 +252,7 @@
     public void testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet() {
         when(mResources.getIntArray(config_tether_upstream_types))
                 .thenReturn(new int[]{TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_HIPRI});
-        when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false);
+        when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
 
         final TetheringConfiguration cfg = new TetheringConfiguration(
                 mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index 9d9ad10..04b2eb4 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -28,6 +28,7 @@
 import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
 import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
 import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
+import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
@@ -72,6 +73,7 @@
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
 import android.net.ITetheringEventCallback;
+import android.net.InetAddresses;
 import android.net.InterfaceConfiguration;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
@@ -81,7 +83,6 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkRequest;
-import android.net.NetworkUtils;
 import android.net.RouteInfo;
 import android.net.TetherStatesParcel;
 import android.net.TetheringConfigurationParcel;
@@ -365,23 +366,26 @@
 
         if (withIPv4) {
             prop.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
-                    NetworkUtils.numericToInetAddress("10.0.0.1"), TEST_MOBILE_IFNAME));
+                    InetAddresses.parseNumericAddress("10.0.0.1"),
+                    TEST_MOBILE_IFNAME, RTN_UNICAST));
         }
 
         if (withIPv6) {
-            prop.addDnsServer(NetworkUtils.numericToInetAddress("2001:db8::2"));
+            prop.addDnsServer(InetAddresses.parseNumericAddress("2001:db8::2"));
             prop.addLinkAddress(
-                    new LinkAddress(NetworkUtils.numericToInetAddress("2001:db8::"),
+                    new LinkAddress(InetAddresses.parseNumericAddress("2001:db8::"),
                             NetworkConstants.RFC7421_PREFIX_LENGTH));
             prop.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0),
-                    NetworkUtils.numericToInetAddress("2001:db8::1"), TEST_MOBILE_IFNAME));
+                    InetAddresses.parseNumericAddress("2001:db8::1"),
+                    TEST_MOBILE_IFNAME, RTN_UNICAST));
         }
 
         if (with464xlat) {
             final LinkProperties stackedLink = new LinkProperties();
             stackedLink.setInterfaceName(TEST_XLAT_MOBILE_IFNAME);
             stackedLink.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
-                    NetworkUtils.numericToInetAddress("192.0.0.1"), TEST_XLAT_MOBILE_IFNAME));
+                    InetAddresses.parseNumericAddress("192.0.0.1"),
+                    TEST_XLAT_MOBILE_IFNAME, RTN_UNICAST));
 
             prop.addStackedLink(stackedLink);
         }
@@ -1210,12 +1214,12 @@
     @Test
     public void testMultiSimAware() throws Exception {
         final TetheringConfiguration initailConfig = mTethering.getTetheringConfiguration();
-        assertEquals(INVALID_SUBSCRIPTION_ID, initailConfig.subId);
+        assertEquals(INVALID_SUBSCRIPTION_ID, initailConfig.activeDataSubId);
 
         final int fakeSubId = 1234;
         mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId);
         final TetheringConfiguration newConfig = mTethering.getTetheringConfiguration();
-        assertEquals(fakeSubId, newConfig.subId);
+        assertEquals(fakeSubId, newConfig.activeDataSubId);
     }
 
     private void workingWifiP2pGroupOwner(
diff --git a/services/Android.bp b/services/Android.bp
index ede4c3e..1e11936 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -61,6 +61,7 @@
         "services.devicepolicy",
         "services.midi",
         "services.net",
+        "services.people",
         "services.print",
         "services.restrictions",
         "services.startop",
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 8d22300..a1f57cb 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -117,6 +117,7 @@
         "android.hardware.oemlock-V1.0-java",
         "android.hardware.configstore-V1.0-java",
         "android.hardware.contexthub-V1.0-java",
+        "android.hardware.rebootescrow-java",
         "android.hardware.soundtrigger-V2.3-java",
         "android.hidl.manager-V1.2-java",
         "dnsresolver_aidl_interface-V2-java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 40a7dfc..312dd46 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -319,6 +319,12 @@
             int deviceOwnerUserId, String deviceOwner, SparseArray<String> profileOwners);
 
     /**
+     * Called by DevicePolicyManagerService to set the package names protected by the device
+     * owner.
+     */
+    public abstract void setDeviceOwnerProtectedPackages(List<String> packageNames);
+
+    /**
      * Returns {@code true} if a given package can't be wiped. Otherwise, returns {@code false}.
      */
     public abstract boolean isPackageDataProtected(int userId, String packageName);
diff --git a/services/core/java/com/android/server/CountryDetectorService.java b/services/core/java/com/android/server/CountryDetectorService.java
index 861c731..b0132d3 100644
--- a/services/core/java/com/android/server/CountryDetectorService.java
+++ b/services/core/java/com/android/server/CountryDetectorService.java
@@ -24,21 +24,29 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.server.location.ComprehensiveCountryDetector;
+import com.android.server.location.CountryDetectorBase;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
 
 /**
- * This class detects the country that the user is in through {@link ComprehensiveCountryDetector}.
+ * This class detects the country that the user is in. The default country detection is made through
+ * {@link com.android.server.location.ComprehensiveCountryDetector}. It is possible to overlay the
+ * detection algorithm by overlaying the attribute R.string.config_customCountryDetector with the
+ * custom class name to use instead. The custom class must extend
+ * {@link com.android.server.location.CountryDetectorBase}
  *
  * @hide
  */
@@ -88,7 +96,7 @@
 
     private final HashMap<IBinder, Receiver> mReceivers;
     private final Context mContext;
-    private ComprehensiveCountryDetector mCountryDetector;
+    private CountryDetectorBase mCountryDetector;
     private boolean mSystemReady;
     private Handler mHandler;
     private CountryListener mLocationBasedDetectorListener;
@@ -184,8 +192,17 @@
                 });
     }
 
-    private void initialize() {
-        mCountryDetector = new ComprehensiveCountryDetector(mContext);
+    @VisibleForTesting
+    void initialize() {
+        final String customCountryClass = mContext.getString(R.string.config_customCountryDetector);
+        if (!TextUtils.isEmpty(customCountryClass)) {
+            mCountryDetector = loadCustomCountryDetectorIfAvailable(customCountryClass);
+        }
+
+        if (mCountryDetector == null) {
+            Slog.d(TAG, "Using default country detector");
+            mCountryDetector = new ComprehensiveCountryDetector(mContext);
+        }
         mLocationBasedDetectorListener = country -> mHandler.post(() -> notifyReceivers(country));
     }
 
@@ -194,10 +211,32 @@
     }
 
     @VisibleForTesting
+    CountryDetectorBase getCountryDetector() {
+        return mCountryDetector;
+    }
+
+    @VisibleForTesting
     boolean isSystemReady() {
         return mSystemReady;
     }
 
+    private CountryDetectorBase loadCustomCountryDetectorIfAvailable(
+            final String customCountryClass) {
+        CountryDetectorBase customCountryDetector = null;
+
+        Slog.d(TAG, "Using custom country detector class: " + customCountryClass);
+        try {
+            customCountryDetector = Class.forName(customCountryClass).asSubclass(
+                    CountryDetectorBase.class).getConstructor(Context.class).newInstance(
+                    mContext);
+        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
+                | NoSuchMethodException | InvocationTargetException e) {
+            Slog.e(TAG, "Could not instantiate the custom country detector class");
+        }
+
+        return customCountryDetector;
+    }
+
     @SuppressWarnings("unused")
     @Override
     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
@@ -206,9 +245,10 @@
         try {
             final Printer p = new PrintWriterPrinter(fout);
             p.println("CountryDetectorService state:");
+            p.println("Country detector class=" + mCountryDetector.getClass().getName());
             p.println("  Number of listeners=" + mReceivers.keySet().size());
             if (mCountryDetector == null) {
-                p.println("  ComprehensiveCountryDetector not initialized");
+                p.println("  CountryDetector not initialized");
             } else {
                 p.println("  " + mCountryDetector.toString());
             }
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
index cfe56052..d20936c 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
@@ -36,11 +36,11 @@
 import android.os.Message;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.os.TimestampedValue;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.NtpTrustedTime;
 import android.util.TimeUtils;
-import android.util.TimestampedValue;
 
 import com.android.internal.util.DumpUtils;
 
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 25bad64..3e6ccb5 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -42,6 +42,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.telephony.Annotation;
+import android.telephony.Annotation.ApnType;
 import android.telephony.Annotation.DataFailureCause;
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.Annotation.SrvccState;
@@ -82,7 +83,6 @@
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.telephony.ITelephonyRegistry;
-import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.util.ArrayUtils;
@@ -269,8 +269,8 @@
     private final LocalLog mListenLog = new LocalLog(100);
 
     // Per-phoneMap of APN Type to DataConnectionState
-    private List<Map<String, PreciseDataConnectionState>> mPreciseDataConnectionStates =
-            new ArrayList<Map<String, PreciseDataConnectionState>>();
+    private List<Map<Integer, PreciseDataConnectionState>> mPreciseDataConnectionStates =
+            new ArrayList<Map<Integer, PreciseDataConnectionState>>();
 
     // Nothing here yet, but putting it here in case we want to add more in the future.
     static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = 0;
@@ -467,7 +467,7 @@
             mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
-            mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
+            mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
         }
     }
 
@@ -551,7 +551,7 @@
             mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
             mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
-            mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
+            mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
         }
 
         mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -1496,11 +1496,12 @@
      *
      * @param phoneId the phoneId carrying the data connection
      * @param subId the subscriptionId for the data connection
-     * @param apnType the APN type that triggered a change in the data connection
+     * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags.
      * @param preciseState a PreciseDataConnectionState that has info about the data connection
      */
+    @Override
     public void notifyDataConnectionForSubscriber(
-            int phoneId, int subId, String apnType, PreciseDataConnectionState preciseState) {
+            int phoneId, int subId, @ApnType int apnType, PreciseDataConnectionState preciseState) {
         if (!checkNotifyPermission("notifyDataConnection()" )) {
             return;
         }
@@ -1526,7 +1527,7 @@
         synchronized (mRecords) {
             if (validatePhoneId(phoneId)) {
                 // We only call the callback when the change is for default APN type.
-                if (PhoneConstants.APN_TYPE_DEFAULT.equals(apnType)
+                if ((ApnSetting.TYPE_DEFAULT & apnType) != 0
                         && (mDataConnectionState[phoneId] != state
                         || mDataConnectionNetworkType[phoneId] != networkType)) {
                     String str = "onDataConnectionStateChanged("
@@ -1595,7 +1596,7 @@
         loge("This function should not be invoked");
     }
 
-    private void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
+    private void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, int apnType) {
         if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
             return;
         }
@@ -1610,7 +1611,7 @@
                         new PreciseDataConnectionState(
                                 TelephonyManager.DATA_UNKNOWN,
                                 TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                                ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+                                apnType, null, null,
                                 DataFailCause.NONE, null));
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
@@ -1779,7 +1780,8 @@
         }
     }
 
-    public void notifyPreciseDataConnectionFailed(int phoneId, int subId, String apnType,
+    @Override
+    public void notifyPreciseDataConnectionFailed(int phoneId, int subId, @ApnType int apnType,
             String apn, @DataFailureCause int failCause) {
         if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) {
             return;
@@ -1795,7 +1797,7 @@
                         new PreciseDataConnectionState(
                                 TelephonyManager.DATA_UNKNOWN,
                                 TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                                ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+                                apnType, null, null,
                                 failCause, null));
                 for (Record r : mRecords) {
                     if (r.matchPhoneStateListenerEvent(
@@ -2177,10 +2179,19 @@
     /** Fired when a subscription's phone state changes. */
     private static final String ACTION_SUBSCRIPTION_PHONE_STATE_CHANGED =
             "android.intent.action.SUBSCRIPTION_PHONE_STATE";
+    /**
+     * Broadcast Action: The data connection state has changed for any one of the
+     * phone's mobile data connections (eg, default, MMS or GPS specific connection).
+     */
+    private static final String ACTION_ANY_DATA_CONNECTION_STATE_CHANGED =
+            "android.intent.action.ANY_DATA_STATE";
 
     // Legacy intent extra keys, copied from PhoneConstants.
     // Used in legacy intents sent here, for backward compatibility.
+    private static final String PHONE_CONSTANTS_DATA_APN_TYPE_KEY = "apnType";
+    private static final String PHONE_CONSTANTS_DATA_APN_KEY = "apn";
     private static final String PHONE_CONSTANTS_SLOT_KEY = "slot";
+    private static final String PHONE_CONSTANTS_STATE_KEY = "state";
     private static final String PHONE_CONSTANTS_SUBSCRIPTION_KEY = "subscription";
 
     private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) {
@@ -2318,19 +2329,50 @@
     }
 
     private void broadcastDataConnectionStateChanged(int state, String apn,
-                                                     String apnType, int subId) {
+                                                     int apnType, int subId) {
         // Note: not reporting to the battery stats service here, because the
         // status bar takes care of that after taking into account all of the
         // required info.
-        Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
-        intent.putExtra(TelephonyManager.EXTRA_STATE, dataStateToString(state));
-
-        intent.putExtra(PhoneConstants.DATA_APN_KEY, apn);
-        intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
+        Intent intent = new Intent(ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+        intent.putExtra(PHONE_CONSTANTS_STATE_KEY, dataStateToString(state));
+        intent.putExtra(PHONE_CONSTANTS_DATA_APN_KEY, apn);
+        intent.putExtra(PHONE_CONSTANTS_DATA_APN_TYPE_KEY, getApnTypesStringFromBitmask(apnType));
         intent.putExtra(PHONE_CONSTANTS_SUBSCRIPTION_KEY, subId);
         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
     }
 
+    private static final Map<Integer, String> APN_TYPE_INT_MAP;
+    static {
+        APN_TYPE_INT_MAP = new android.util.ArrayMap<Integer, String>();
+        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_DEFAULT, "default");
+        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_MMS, "mms");
+        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_SUPL, "supl");
+        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_DUN, "dun");
+        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_HIPRI, "hipri");
+        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_FOTA, "fota");
+        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_IMS, "ims");
+        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_CBS, "cbs");
+        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_IA, "ia");
+        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_EMERGENCY, "emergency");
+        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_MCX, "mcx");
+        APN_TYPE_INT_MAP.put(ApnSetting.TYPE_XCAP, "xcap");
+    }
+
+    /**
+     * Copy of ApnSetting#getApnTypesStringFromBitmask for legacy broadcast.
+     * @param apnTypeBitmask bitmask of APN types.
+     * @return comma delimited list of APN types.
+     */
+    private static String getApnTypesStringFromBitmask(int apnTypeBitmask) {
+        List<String> types = new ArrayList<>();
+        for (Integer type : APN_TYPE_INT_MAP.keySet()) {
+            if ((apnTypeBitmask & type) == type) {
+                types.add(APN_TYPE_INT_MAP.get(type));
+            }
+        }
+        return android.text.TextUtils.join(",", types);
+    }
+
     private void enforceNotifyPermissionOrCarrierPrivilege(String method) {
         if (checkNotifyPermission()) {
             return;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4bc0297..c21adb0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18673,6 +18673,11 @@
         }
 
         @Override
+        public void monitor() {
+            ActivityManagerService.this.monitor();
+        }
+
+        @Override
         public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) {
             synchronized (ActivityManagerService.this) {
                 return ActivityManagerService.this.inputDispatchingTimedOut(
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index a4c44fa..d7ad1c2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2934,33 +2934,37 @@
         }
         ArraySet<Long> enabled = new ArraySet<>();
         ArraySet<Long> disabled = new ArraySet<>();
-        switch (toggleValue) {
-            case "enable":
-                enabled.add(changeId);
-                pw.println("Enabled change " + changeId + " for " + packageName + ".");
-                CompatibilityChangeConfig overrides =
-                        new CompatibilityChangeConfig(
-                                new Compatibility.ChangeConfig(enabled, disabled));
-                platformCompat.setOverrides(overrides, packageName);
-                return 0;
-            case "disable":
-                disabled.add(changeId);
-                pw.println("Disabled change " + changeId + " for " + packageName + ".");
-                overrides =
-                        new CompatibilityChangeConfig(
-                                new Compatibility.ChangeConfig(enabled, disabled));
-                platformCompat.setOverrides(overrides, packageName);
-                return 0;
-            case "reset":
-                if (platformCompat.clearOverride(changeId, packageName)) {
-                    pw.println("Reset change " + changeId + " for " + packageName
-                            + " to default value.");
-                } else {
-                    pw.println("No override exists for changeId " + changeId + ".");
-                }
-                return 0;
-            default:
-                pw.println("Invalid toggle value: '" + toggleValue + "'.");
+        try {
+            switch (toggleValue) {
+                case "enable":
+                    enabled.add(changeId);
+                    CompatibilityChangeConfig overrides =
+                            new CompatibilityChangeConfig(
+                                    new Compatibility.ChangeConfig(enabled, disabled));
+                    platformCompat.setOverrides(overrides, packageName);
+                    pw.println("Enabled change " + changeId + " for " + packageName + ".");
+                    return 0;
+                case "disable":
+                    disabled.add(changeId);
+                    overrides =
+                            new CompatibilityChangeConfig(
+                                    new Compatibility.ChangeConfig(enabled, disabled));
+                    platformCompat.setOverrides(overrides, packageName);
+                    pw.println("Disabled change " + changeId + " for " + packageName + ".");
+                    return 0;
+                case "reset":
+                    if (platformCompat.clearOverride(changeId, packageName)) {
+                        pw.println("Reset change " + changeId + " for " + packageName
+                                + " to default value.");
+                    } else {
+                        pw.println("No override exists for changeId " + changeId + ".");
+                    }
+                    return 0;
+                default:
+                    pw.println("Invalid toggle value: '" + toggleValue + "'.");
+            }
+        } catch (SecurityException e) {
+            pw.println(e.getMessage());
         }
         return -1;
     }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 5bbb517..a7593c7 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -17,6 +17,11 @@
 package com.android.server.appop;
 
 import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+import static android.app.AppOpsManager.FILTER_BY_FEATURE_ID;
+import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
+import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
 import static android.app.AppOpsManager.NoteOpEvent;
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_COARSE_LOCATION;
@@ -53,7 +58,6 @@
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.HistoricalOps;
-import android.app.AppOpsManager.HistoricalOpsRequest;
 import android.app.AppOpsManager.Mode;
 import android.app.AppOpsManager.OpEntry;
 import android.app.AppOpsManager.OpFeatureEntry;
@@ -632,6 +636,7 @@
     }
 
     private final class FeatureOp {
+        public final @Nullable String featureId;
         public final @NonNull Op parent;
 
         /**
@@ -658,7 +663,8 @@
         @GuardedBy("AppOpsService.this")
         private @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents;
 
-        FeatureOp(@NonNull Op parent) {
+        FeatureOp(@Nullable String featureId, @NonNull Op parent) {
+            this.featureId = featureId;
             this.parent = parent;
         }
 
@@ -676,6 +682,9 @@
                 @OpFlags int flags) {
             accessed(System.currentTimeMillis(), -1, proxyUid, proxyPackageName,
                     proxyFeatureId, uidState, flags);
+
+            mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName,
+                    featureId, uidState, flags);
         }
 
         /**
@@ -720,6 +729,9 @@
          */
         public void rejected(@AppOpsManager.UidState int uidState, @OpFlags int flags) {
             rejected(System.currentTimeMillis(), uidState, flags);
+
+            mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid, parent.packageName,
+                    featureId, uidState, flags);
         }
 
         /**
@@ -780,7 +792,7 @@
 
             // startOp events don't support proxy, hence use flags==SELF
             mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName,
-                    uidState, OP_FLAG_SELF);
+                    featureId, uidState, OP_FLAG_SELF);
         }
 
         /**
@@ -820,8 +832,8 @@
                 mAccessEvents.put(makeKey(event.getUidState(), OP_FLAG_SELF), finishedEvent);
 
                 mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
-                        parent.packageName, event.getUidState(), AppOpsManager.OP_FLAG_SELF,
-                        finishedEvent.getDuration());
+                        parent.packageName, featureId, event.getUidState(),
+                        AppOpsManager.OP_FLAG_SELF, finishedEvent.getDuration());
 
                 mInProgressStartOpEventPool.release(event);
 
@@ -1031,7 +1043,7 @@
 
             featureOp = mFeatures.get(featureId);
             if (featureOp == null) {
-                featureOp = new FeatureOp(parent);
+                featureOp = new FeatureOp(featureId, parent);
                 mFeatures.put(featureId, featureOp);
             }
 
@@ -1697,18 +1709,47 @@
         }
     }
 
+    /**
+     * Verify that historical appop request arguments are valid.
+     */
+    private void ensureHistoricalOpRequestIsValid(int uid, String packageName, String featureId,
+            List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
+            int flags) {
+        if ((filter & FILTER_BY_UID) != 0) {
+            Preconditions.checkArgument(uid != Process.INVALID_UID);
+        } else {
+            Preconditions.checkArgument(uid == Process.INVALID_UID);
+        }
+
+        if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
+            Objects.requireNonNull(packageName);
+        } else {
+            Preconditions.checkArgument(packageName == null);
+        }
+
+        if ((filter & FILTER_BY_FEATURE_ID) == 0) {
+            Preconditions.checkArgument(featureId == null);
+        }
+
+        if ((filter & FILTER_BY_OP_NAMES) != 0) {
+            Objects.requireNonNull(opNames);
+        } else {
+            Preconditions.checkArgument(opNames == null);
+        }
+
+        Preconditions.checkFlagsArgument(filter,
+                FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_FEATURE_ID | FILTER_BY_OP_NAMES);
+        Preconditions.checkArgumentNonnegative(beginTimeMillis);
+        Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
+        Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
+    }
+
     @Override
-    public void getHistoricalOps(int uid, @NonNull String packageName,
-            @Nullable List<String> opNames, long beginTimeMillis, long endTimeMillis,
-            @OpFlags int flags, @NonNull RemoteCallback callback) {
-        // Use the builder to validate arguments.
-        new HistoricalOpsRequest.Builder(
-                beginTimeMillis, endTimeMillis)
-                .setUid(uid)
-                .setPackageName(packageName)
-                .setOpNames(opNames)
-                .setFlags(flags)
-                .build();
+    public void getHistoricalOps(int uid, String packageName, String featureId,
+            List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
+            int flags, RemoteCallback callback) {
+        ensureHistoricalOpRequestIsValid(uid, packageName, featureId, opNames, filter,
+                beginTimeMillis, endTimeMillis, flags);
         Objects.requireNonNull(callback, "callback cannot be null");
 
         mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
@@ -1718,22 +1759,16 @@
                 ? opNames.toArray(new String[opNames.size()]) : null;
 
         // Must not hold the appops lock
-        mHistoricalRegistry.getHistoricalOps(uid, packageName, opNamesArray,
+        mHistoricalRegistry.getHistoricalOps(uid, packageName, featureId, opNamesArray, filter,
                 beginTimeMillis, endTimeMillis, flags, callback);
     }
 
     @Override
-    public void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
-            @Nullable List<String> opNames, long beginTimeMillis, long endTimeMillis,
-            @OpFlags int flags, @NonNull RemoteCallback callback) {
-        // Use the builder to validate arguments.
-        new HistoricalOpsRequest.Builder(
-                beginTimeMillis, endTimeMillis)
-                .setUid(uid)
-                .setPackageName(packageName)
-                .setOpNames(opNames)
-                .setFlags(flags)
-                .build();
+    public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String featureId,
+            List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
+            int flags, RemoteCallback callback) {
+        ensureHistoricalOpRequestIsValid(uid, packageName, featureId, opNames, filter,
+                beginTimeMillis, endTimeMillis, flags);
         Objects.requireNonNull(callback, "callback cannot be null");
 
         mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
@@ -1743,8 +1778,8 @@
                 ? opNames.toArray(new String[opNames.size()]) : null;
 
         // Must not hold the appops lock
-        mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, opNamesArray,
-                beginTimeMillis, endTimeMillis, flags, callback);
+        mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, featureId, opNamesArray,
+                filter, beginTimeMillis, endTimeMillis, flags, callback);
     }
 
     @Override
@@ -2631,8 +2666,6 @@
                             + switchCode + " (" + code + ") uid " + uid + " package "
                             + packageName);
                     featureOp.rejected(uidState.state, flags);
-                    mHistoricalRegistry.incrementOpRejected(code, uid, packageName,
-                            uidState.state, flags);
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode);
                     return uidMode;
                 }
@@ -2645,8 +2678,6 @@
                             + switchCode + " (" + code + ") uid " + uid + " package "
                             + packageName);
                     featureOp.rejected(uidState.state, flags);
-                    mHistoricalRegistry.incrementOpRejected(code, uid, packageName,
-                            uidState.state, flags);
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, mode);
                     return mode;
                 }
@@ -2654,9 +2685,6 @@
             if (DEBUG) Slog.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
                     + " package " + packageName + (featureId == null ? "" : "." + featureId));
             featureOp.accessed(proxyUid, proxyPackageName, proxyFeatureId, uidState.state, flags);
-            // TODO moltmann: Add features to historical app-ops
-            mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName,
-                    uidState.state, flags);
             scheduleOpNotedIfNeededLocked(code, uid, packageName,
                     AppOpsManager.MODE_ALLOWED);
 
@@ -2938,8 +2966,6 @@
                             + switchCode + " (" + code + ") uid " + uid + " package "
                             + resolvedPackageName);
                     featureOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF);
-                    mHistoricalRegistry.incrementOpRejected(opCode, uid, packageName,
-                            uidState.state, AppOpsManager.OP_FLAG_SELF);
                     return uidMode;
                 }
             } else {
@@ -2952,8 +2978,6 @@
                             + switchCode + " (" + code + ") uid " + uid + " package "
                             + resolvedPackageName);
                     featureOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF);
-                    mHistoricalRegistry.incrementOpRejected(opCode, uid, packageName,
-                            uidState.state, AppOpsManager.OP_FLAG_SELF);
                     return mode;
                 }
             }
@@ -4437,16 +4461,24 @@
         pw.println("    Limit output to data associated with the given app op mode.");
         pw.println("  --package [PACKAGE]");
         pw.println("    Limit output to data associated with the given package name.");
+        pw.println("  --featureId [featureId]");
+        pw.println("    Limit output to data associated with the given feature id.");
         pw.println("  --watchers");
         pw.println("    Only output the watcher sections.");
         pw.println("  --history");
         pw.println("    Output the historical data.");
     }
 
-    private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
-            long now, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
+    private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterFeatureId,
+            @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
+            @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
         final int numFeatures = op.mFeatures.size();
         for (int i = 0; i < numFeatures; i++) {
+            if ((filter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(op.mFeatures.keyAt(i),
+                    filterFeatureId)) {
+                continue;
+            }
+
             pw.print(prefix + op.mFeatures.keyAt(i) + "=[\n");
             dumpStatesLocked(pw, nowElapsed, op, op.mFeatures.keyAt(i), now, sdf, date,
                     prefix + "  ");
@@ -4563,10 +4595,12 @@
 
         int dumpOp = OP_NONE;
         String dumpPackage = null;
+        String dumpFeatureId = null;
         int dumpUid = Process.INVALID_UID;
         int dumpMode = -1;
         boolean dumpWatchers = false;
         boolean dumpHistory = false;
+        @HistoricalOpsRequestFilter int dumpFilter = 0;
 
         if (args != null) {
             for (int i=0; i<args.length; i++) {
@@ -4583,6 +4617,7 @@
                         return;
                     }
                     dumpOp = Shell.strOpToOp(args[i], pw);
+                    dumpFilter |= FILTER_BY_OP_NAMES;
                     if (dumpOp < 0) {
                         return;
                     }
@@ -4593,6 +4628,7 @@
                         return;
                     }
                     dumpPackage = args[i];
+                    dumpFilter |= FILTER_BY_PACKAGE_NAME;
                     try {
                         dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
                                 PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
@@ -4604,6 +4640,15 @@
                         return;
                     }
                     dumpUid = UserHandle.getAppId(dumpUid);
+                    dumpFilter |= FILTER_BY_UID;
+                } else if ("--featureId".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("No argument for --featureId option");
+                        return;
+                    }
+                    dumpFeatureId = args[i];
+                    dumpFilter |= FILTER_BY_FEATURE_ID;
                 } else if ("--mode".equals(arg)) {
                     i++;
                     if (i >= args.length) {
@@ -4640,8 +4685,8 @@
             final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
             final Date date = new Date();
             boolean needSep = false;
-            if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null
-                    && !dumpWatchers && !dumpHistory) {
+            if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
+                    && !dumpHistory) {
                 pw.println("  Profile owners:");
                 for (int poi = 0; poi < mProfileOwners.size(); poi++) {
                     pw.print("    User #");
@@ -4944,7 +4989,8 @@
                             pw.print("="); pw.print(AppOpsManager.modeToName(mode));
                         }
                         pw.println("): ");
-                        dumpStatesLocked(pw, nowElapsed, op, now, sdf, date, "        ");
+                        dumpStatesLocked(pw, dumpFeatureId, dumpFilter, nowElapsed, op, now, sdf,
+                                date, "        ");
                     }
                 }
             }
@@ -5043,7 +5089,8 @@
 
         // Must not hold the appops lock
         if (dumpHistory && !dumpWatchers) {
-            mHistoricalRegistry.dump("  ", pw, dumpUid, dumpPackage, dumpOp);
+            mHistoricalRegistry.dump("  ", pw, dumpUid, dumpPackage, dumpFeatureId, dumpOp,
+                    dumpFilter);
         }
     }
 
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 2175ca0..cd450d4 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -15,12 +15,19 @@
  */
 package com.android.server.appop;
 
+import static android.app.AppOpsManager.FILTER_BY_FEATURE_ID;
+import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalFeatureOps;
 import android.app.AppOpsManager.HistoricalMode;
 import android.app.AppOpsManager.HistoricalOp;
 import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalOpsRequestFilter;
 import android.app.AppOpsManager.HistoricalPackageOps;
 import android.app.AppOpsManager.HistoricalUidOps;
 import android.app.AppOpsManager.OpFlags;
@@ -72,6 +79,7 @@
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
@@ -273,8 +281,9 @@
                 + "=" + setting + " resetting!");
     }
 
-    void dump(String prefix, PrintWriter pw, int filterUid,
-              String filterPackage, int filterOp) {
+    void dump(String prefix, PrintWriter pw, int filterUid, @Nullable String filterPackage,
+            @Nullable String filterFeatureId, int filterOp,
+            @HistoricalOpsRequestFilter int filter) {
         if (!isApiEnabled()) {
             return;
         }
@@ -289,7 +298,7 @@
                 pw.println(AppOpsManager.historicalModeToString(mMode));
 
                 final StringDumpVisitor visitor = new StringDumpVisitor(prefix + "  ",
-                        pw, filterUid, filterPackage, filterOp);
+                        pw, filterUid, filterPackage, filterFeatureId, filterOp, filter);
                 final long nowMillis = System.currentTimeMillis();
 
                 // Dump in memory state first
@@ -329,7 +338,8 @@
     }
 
     void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
-            @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+            @Nullable String featureId, @Nullable String[] opNames,
+            @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis,
             @OpFlags int flags, @NonNull RemoteCallback callback) {
         if (!isApiEnabled()) {
             callback.sendResult(new Bundle());
@@ -344,8 +354,8 @@
                     return;
                 }
                 final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis);
-                mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames,
-                        beginTimeMillis, endTimeMillis, flags);
+                mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, featureId,
+                        opNames, filter, beginTimeMillis, endTimeMillis, flags);
                 final Bundle payload = new Bundle();
                 payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
                 callback.sendResult(payload);
@@ -353,9 +363,10 @@
         }
     }
 
-    void getHistoricalOps(int uid, @NonNull String packageName,
-            @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
-            @OpFlags int flags, @NonNull RemoteCallback callback) {
+    void getHistoricalOps(int uid, @NonNull String packageName, @Nullable String featureId,
+            @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
+            long beginTimeMillis, long endTimeMillis, @OpFlags int flags,
+            @NonNull RemoteCallback callback) {
         if (!isApiEnabled()) {
             callback.sendResult(new Bundle());
             return;
@@ -390,8 +401,8 @@
                         || inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) {
                     // Some of the current batch falls into the query, so extract that.
                     final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps);
-                    currentOpsCopy.filter(uid, packageName, opNames, inMemoryAdjBeginTimeMillis,
-                            inMemoryAdjEndTimeMillis);
+                    currentOpsCopy.filter(uid, packageName, featureId, opNames, filter,
+                            inMemoryAdjBeginTimeMillis, inMemoryAdjEndTimeMillis);
                     result.merge(currentOpsCopy);
                 }
                 pendingWrites = new ArrayList<>(mPendingWrites);
@@ -410,8 +421,8 @@
                         - onDiskAndInMemoryOffsetMillis, 0);
                 final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis
                         - onDiskAndInMemoryOffsetMillis, 0);
-                mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames,
-                        onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, flags);
+                mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, featureId,
+                        opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, flags);
             }
 
             // Rebase the result time to be since epoch.
@@ -425,43 +436,47 @@
     }
 
     void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
-            @UidState int uidState, @OpFlags int flags) {
+            @Nullable String featureId, @UidState int uidState, @OpFlags int flags) {
         synchronized (mInMemoryLock) {
             if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
                 if (!isPersistenceInitializedMLocked()) {
                     Slog.e(LOG_TAG, "Interaction before persistence initialized");
                     return;
                 }
-                getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
-                        .increaseAccessCount(op, uid, packageName, uidState, flags, 1);
+                getUpdatedPendingHistoricalOpsMLocked(
+                        System.currentTimeMillis()).increaseAccessCount(op, uid, packageName,
+                        featureId, uidState, flags, 1);
             }
         }
     }
 
     void incrementOpRejected(int op, int uid, @NonNull String packageName,
-            @UidState int uidState, @OpFlags int flags) {
+            @Nullable String featureId, @UidState int uidState, @OpFlags int flags) {
         synchronized (mInMemoryLock) {
             if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
                 if (!isPersistenceInitializedMLocked()) {
                     Slog.e(LOG_TAG, "Interaction before persistence initialized");
                     return;
                 }
-                getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
-                        .increaseRejectCount(op, uid, packageName, uidState, flags, 1);
+                getUpdatedPendingHistoricalOpsMLocked(
+                        System.currentTimeMillis()).increaseRejectCount(op, uid, packageName,
+                        featureId, uidState, flags, 1);
             }
         }
     }
 
     void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
-            @UidState int uidState, @OpFlags int flags, long increment) {
+            @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+            long increment) {
         synchronized (mInMemoryLock) {
             if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
                 if (!isPersistenceInitializedMLocked()) {
                     Slog.e(LOG_TAG, "Interaction before persistence initialized");
                     return;
                 }
-                getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
-                        .increaseAccessDuration(op, uid, packageName, uidState, flags, increment);
+                getUpdatedPendingHistoricalOpsMLocked(
+                        System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName,
+                        featureId, uidState, flags, increment);
             }
         }
     }
@@ -713,6 +728,7 @@
         private static final String TAG_OPS = "ops";
         private static final String TAG_UID = "uid";
         private static final String TAG_PACKAGE = "pkg";
+        private static final String TAG_FEATURE = "ftr";
         private static final String TAG_OP = "op";
         private static final String TAG_STATE = "st";
 
@@ -791,8 +807,8 @@
 
         @Nullable List<HistoricalOps> readHistoryRawDLocked() {
             return collectHistoricalOpsBaseDLocked(Process.INVALID_UID /*filterUid*/,
-                    null /*filterPackageName*/, null /*filterOpNames*/,
-                    0 /*filterBeginTimeMills*/, Long.MAX_VALUE /*filterEndTimeMills*/,
+                    null /*filterPackageName*/, null /*filterFeatureId*/, null /*filterOpNames*/,
+                    0 /*filter*/, 0 /*filterBeginTimeMills*/, Long.MAX_VALUE /*filterEndTimeMills*/,
                     AppOpsManager.OP_FLAGS_ALL);
         }
 
@@ -846,11 +862,12 @@
         }
 
         private void collectHistoricalOpsDLocked(@NonNull HistoricalOps currentOps,
-                int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
+                int filterUid, @Nullable String filterPackageName, @Nullable String filterFeatureId,
+                @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
                 long filterBeingMillis, long filterEndMillis, @OpFlags int filterFlags) {
             final List<HistoricalOps> readOps = collectHistoricalOpsBaseDLocked(filterUid,
-                    filterPackageName, filterOpNames, filterBeingMillis, filterEndMillis,
-                    filterFlags);
+                    filterPackageName, filterFeatureId, filterOpNames, filter, filterBeingMillis,
+                    filterEndMillis, filterFlags);
             if (readOps != null) {
                 final int readCount = readOps.size();
                 for (int i = 0; i < readCount; i++) {
@@ -861,7 +878,8 @@
         }
 
         private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked(
-                int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
+                int filterUid, @Nullable String filterPackageName, @Nullable String filterFeatureId,
+                @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
                 long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags) {
             File baseDir = null;
             try {
@@ -874,9 +892,9 @@
                 final Set<String> historyFiles = getHistoricalFileNames(baseDir);
                 final long[] globalContentOffsetMillis = {0};
                 final LinkedList<HistoricalOps> ops = collectHistoricalOpsRecursiveDLocked(
-                        baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
-                        filterEndTimeMillis, filterFlags, globalContentOffsetMillis,
-                        null /*outOps*/, 0 /*depth*/, historyFiles);
+                        baseDir, filterUid, filterPackageName, filterFeatureId, filterOpNames,
+                        filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
+                        globalContentOffsetMillis, null /*outOps*/, 0 /*depth*/, historyFiles);
                 if (DEBUG) {
                     filesInvariant.stopTracking(baseDir);
                 }
@@ -890,8 +908,9 @@
         }
 
         private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsRecursiveDLocked(
-                @NonNull File baseDir, int filterUid, @NonNull String filterPackageName,
-                @Nullable String[] filterOpNames, long filterBeginTimeMillis,
+                @NonNull File baseDir, int filterUid, @Nullable String filterPackageName,
+                @Nullable String filterFeatureId, @Nullable String[] filterOpNames,
+                @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis,
                 long filterEndTimeMillis, @OpFlags int filterFlags,
                 @NonNull long[] globalContentOffsetMillis,
                 @Nullable LinkedList<HistoricalOps> outOps, int depth,
@@ -908,17 +927,17 @@
             // Read historical data at this level
             final List<HistoricalOps> readOps = readHistoricalOpsLocked(baseDir,
                     previousIntervalEndMillis, currentIntervalEndMillis, filterUid,
-                    filterPackageName, filterOpNames, filterBeginTimeMillis, filterEndTimeMillis,
-                    filterFlags, globalContentOffsetMillis, depth, historyFiles);
-
+                    filterPackageName, filterFeatureId, filterOpNames, filter,
+                    filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
+                    globalContentOffsetMillis, depth, historyFiles);
             // Empty is a special signal to stop diving
             if (readOps != null && readOps.isEmpty()) {
                 return outOps;
             }
 
             // Collect older historical data from subsequent levels
-            outOps = collectHistoricalOpsRecursiveDLocked(
-                    baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
+            outOps = collectHistoricalOpsRecursiveDLocked(baseDir, filterUid, filterPackageName,
+                    filterFeatureId, filterOpNames, filter, filterBeginTimeMillis,
                     filterEndTimeMillis, filterFlags, globalContentOffsetMillis, outOps, depth + 1,
                     historyFiles);
 
@@ -987,10 +1006,10 @@
             final List<HistoricalOps> existingOps = readHistoricalOpsLocked(oldBaseDir,
                     previousIntervalEndMillis, currentIntervalEndMillis,
                     Process.INVALID_UID /*filterUid*/, null /*filterPackageName*/,
-                    null /*filterOpNames*/, Long.MIN_VALUE /*filterBeginTimeMillis*/,
-                    Long.MAX_VALUE /*filterEndTimeMillis*/, AppOpsManager.OP_FLAGS_ALL,
-                    null, depth, null /*historyFiles*/);
-
+                    null /*filterFeatureId*/, null /*filterOpNames*/, 0 /*filter*/,
+                    Long.MIN_VALUE /*filterBeginTimeMillis*/,
+                    Long.MAX_VALUE /*filterEndTimeMillis*/, AppOpsManager.OP_FLAGS_ALL, null, depth,
+                    null /*historyFiles*/);
             if (DEBUG) {
                 enforceOpsWellFormed(existingOps);
             }
@@ -1100,8 +1119,9 @@
         }
 
         private @Nullable List<HistoricalOps> readHistoricalOpsLocked(File baseDir,
-                long intervalBeginMillis, long intervalEndMillis,
-                int filterUid, @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+                long intervalBeginMillis, long intervalEndMillis, int filterUid,
+                @Nullable String filterPackageName, @Nullable String filterFeatureId,
+                @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
                 long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags,
                 @Nullable long[] cumulativeOverflowMillis, int depth,
                 @NonNull Set<String> historyFiles)
@@ -1127,13 +1147,14 @@
                     return null;
                 }
             }
-            return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterOpNames,
-                    filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
+            return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterFeatureId,
+                    filterOpNames, filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
                     cumulativeOverflowMillis);
         }
 
         private @Nullable List<HistoricalOps> readHistoricalOpsLocked(@NonNull File file,
-                int filterUid, @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+                int filterUid, @Nullable String filterPackageName, @Nullable String filterFeatureId,
+                @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
                 long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags,
                 @Nullable long[] cumulativeOverflowMillis)
                 throws IOException, XmlPullParserException {
@@ -1158,9 +1179,10 @@
                 final int depth = parser.getDepth();
                 while (XmlUtils.nextElementWithin(parser, depth)) {
                     if (TAG_OPS.equals(parser.getName())) {
-                        final HistoricalOps ops = readeHistoricalOpsDLocked(parser,
-                                filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
-                                filterEndTimeMillis, filterFlags, cumulativeOverflowMillis);
+                        final HistoricalOps ops = readeHistoricalOpsDLocked(parser, filterUid,
+                                filterPackageName, filterFeatureId, filterOpNames, filter,
+                                filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
+                                cumulativeOverflowMillis);
                         if (ops == null) {
                             continue;
                         }
@@ -1193,7 +1215,8 @@
 
         private @Nullable HistoricalOps readeHistoricalOpsDLocked(
                 @NonNull XmlPullParser parser, int filterUid, @Nullable String filterPackageName,
-                @Nullable String[] filterOpNames, long filterBeginTimeMillis,
+                @Nullable String filterFeatureId, @Nullable String[] filterOpNames,
+                @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis,
                 long filterEndTimeMillis, @OpFlags int filterFlags,
                 @Nullable long[] cumulativeOverflowMillis)
                 throws IOException, XmlPullParserException {
@@ -1222,7 +1245,8 @@
             while (XmlUtils.nextElementWithin(parser, depth)) {
                 if (TAG_UID.equals(parser.getName())) {
                     final HistoricalOps returnedOps = readHistoricalUidOpsDLocked(ops, parser,
-                            filterUid, filterPackageName, filterOpNames, filterFlags, filterScale);
+                            filterUid, filterPackageName, filterFeatureId, filterOpNames, filter,
+                            filterFlags, filterScale);
                     if (ops == null) {
                         ops = returnedOps;
                     }
@@ -1236,11 +1260,12 @@
 
         private @Nullable HistoricalOps readHistoricalUidOpsDLocked(
                 @Nullable HistoricalOps ops, @NonNull XmlPullParser parser, int filterUid,
-                @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+                @Nullable String filterPackageName, @Nullable String filterFeatureId,
+                @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
                 @OpFlags int filterFlags, double filterScale)
                 throws IOException, XmlPullParserException {
             final int uid = XmlUtils.readIntAttribute(parser, ATTR_NAME);
-            if (filterUid != Process.INVALID_UID && filterUid != uid) {
+            if ((filter & FILTER_BY_UID) != 0 && filterUid != uid) {
                 XmlUtils.skipCurrentTag(parser);
                 return null;
             }
@@ -1248,8 +1273,8 @@
             while (XmlUtils.nextElementWithin(parser, depth)) {
                 if (TAG_PACKAGE.equals(parser.getName())) {
                     final HistoricalOps returnedOps = readHistoricalPackageOpsDLocked(ops,
-                            uid, parser, filterPackageName, filterOpNames, filterFlags,
-                            filterScale);
+                            uid, parser, filterPackageName, filterFeatureId, filterOpNames, filter,
+                            filterFlags, filterScale);
                     if (ops == null) {
                         ops = returnedOps;
                     }
@@ -1260,19 +1285,46 @@
 
         private @Nullable HistoricalOps readHistoricalPackageOpsDLocked(
                 @Nullable HistoricalOps ops, int uid, @NonNull XmlPullParser parser,
-                @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+                @Nullable String filterPackageName, @Nullable String filterFeatureId,
+                @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
                 @OpFlags int filterFlags, double filterScale)
                 throws IOException, XmlPullParserException {
             final String packageName = XmlUtils.readStringAttribute(parser, ATTR_NAME);
-            if (filterPackageName != null && !filterPackageName.equals(packageName)) {
+            if ((filter & FILTER_BY_PACKAGE_NAME) != 0 && !filterPackageName.equals(packageName)) {
+                XmlUtils.skipCurrentTag(parser);
+                return null;
+            }
+            final int depth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, depth)) {
+                if (TAG_FEATURE.equals(parser.getName())) {
+                    final HistoricalOps returnedOps = readHistoricalFeatureOpsDLocked(ops, uid,
+                            packageName, parser, filterFeatureId, filterOpNames, filter,
+                            filterFlags, filterScale);
+                    if (ops == null) {
+                        ops = returnedOps;
+                    }
+                }
+            }
+            return ops;
+        }
+
+        private @Nullable HistoricalOps readHistoricalFeatureOpsDLocked(@Nullable HistoricalOps ops,
+                int uid, String packageName, @NonNull XmlPullParser parser,
+                @Nullable String filterFeatureId, @Nullable String[] filterOpNames,
+                @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags,
+                double filterScale)
+                throws IOException, XmlPullParserException {
+            final String featureId = XmlUtils.readStringAttribute(parser, ATTR_NAME);
+            if ((filter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(filterFeatureId,
+                    featureId)) {
                 XmlUtils.skipCurrentTag(parser);
                 return null;
             }
             final int depth = parser.getDepth();
             while (XmlUtils.nextElementWithin(parser, depth)) {
                 if (TAG_OP.equals(parser.getName())) {
-                    final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid,
-                            packageName, parser, filterOpNames, filterFlags, filterScale);
+                    final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid, packageName,
+                            featureId, parser, filterOpNames, filter, filterFlags, filterScale);
                     if (ops == null) {
                         ops = returnedOps;
                     }
@@ -1282,11 +1334,13 @@
         }
 
         private @Nullable HistoricalOps readHistoricalOpDLocked(@Nullable HistoricalOps ops,
-                int uid, String packageName, @NonNull XmlPullParser parser,
-                @Nullable String[] filterOpNames, @OpFlags int filterFlags, double filterScale)
+                int uid, @NonNull String packageName, @Nullable String featureId,
+                @NonNull XmlPullParser parser, @Nullable String[] filterOpNames,
+                @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags,
+                double filterScale)
                 throws IOException, XmlPullParserException {
             final int op = XmlUtils.readIntAttribute(parser, ATTR_NAME);
-            if (filterOpNames != null && !ArrayUtils.contains(filterOpNames,
+            if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(filterOpNames,
                     AppOpsManager.opToPublicName(op))) {
                 XmlUtils.skipCurrentTag(parser);
                 return null;
@@ -1295,7 +1349,7 @@
             while (XmlUtils.nextElementWithin(parser, depth)) {
                 if (TAG_STATE.equals(parser.getName())) {
                     final HistoricalOps returnedOps = readStateDLocked(ops, uid,
-                            packageName, op, parser, filterFlags, filterScale);
+                            packageName, featureId, op, parser, filterFlags, filterScale);
                     if (ops == null) {
                         ops = returnedOps;
                     }
@@ -1305,8 +1359,9 @@
         }
 
         private @Nullable HistoricalOps readStateDLocked(@Nullable HistoricalOps ops,
-                int uid, String packageName, int op, @NonNull XmlPullParser parser,
-                @OpFlags int filterFlags, double filterScale) throws IOException {
+                int uid, @NonNull String packageName, @Nullable String featureId, int op,
+                @NonNull XmlPullParser parser, @OpFlags int filterFlags, double filterScale)
+                throws IOException {
             final long key = XmlUtils.readLongAttribute(parser, ATTR_NAME);
             final int flags = AppOpsManager.extractFlagsFromKey(key) & filterFlags;
             if (flags == 0) {
@@ -1322,7 +1377,8 @@
                 if (ops == null) {
                     ops = new HistoricalOps(0, 0);
                 }
-                ops.increaseAccessCount(op, uid, packageName, uidState, flags, accessCount);
+                ops.increaseAccessCount(op, uid, packageName, featureId, uidState, flags,
+                        accessCount);
             }
             long rejectCount = XmlUtils.readLongAttribute(parser, ATTR_REJECT_COUNT, 0);
             if (rejectCount > 0) {
@@ -1333,7 +1389,8 @@
                 if (ops == null) {
                     ops = new HistoricalOps(0, 0);
                 }
-                ops.increaseRejectCount(op, uid, packageName, uidState, flags, rejectCount);
+                ops.increaseRejectCount(op, uid, packageName, featureId, uidState, flags,
+                        rejectCount);
             }
             long accessDuration =  XmlUtils.readLongAttribute(parser, ATTR_ACCESS_DURATION, 0);
             if (accessDuration > 0) {
@@ -1344,7 +1401,8 @@
                 if (ops == null) {
                     ops = new HistoricalOps(0, 0);
                 }
-                ops.increaseAccessDuration(op, uid, packageName, uidState, flags, accessDuration);
+                ops.increaseAccessDuration(op, uid, packageName, featureId, uidState, flags,
+                        accessDuration);
             }
             return ops;
         }
@@ -1409,14 +1467,26 @@
                 @NonNull XmlSerializer serializer) throws IOException {
             serializer.startTag(null, TAG_PACKAGE);
             serializer.attribute(null, ATTR_NAME, packageOps.getPackageName());
-            final int opCount = packageOps.getOpCount();
-            for (int i = 0; i < opCount; i++) {
-                final HistoricalOp op = packageOps.getOpAt(i);
-                writeHistoricalOpDLocked(op, serializer);
+            final int numFeatures = packageOps.getFeatureCount();
+            for (int i = 0; i < numFeatures; i++) {
+                final HistoricalFeatureOps op = packageOps.getFeatureOpsAt(i);
+                writeHistoricalFeatureOpsDLocked(op, serializer);
             }
             serializer.endTag(null, TAG_PACKAGE);
         }
 
+        private void writeHistoricalFeatureOpsDLocked(@NonNull HistoricalFeatureOps featureOps,
+                @NonNull XmlSerializer serializer) throws IOException {
+            serializer.startTag(null, TAG_FEATURE);
+            XmlUtils.writeStringAttribute(serializer, ATTR_NAME, featureOps.getFeatureId());
+            final int opCount = featureOps.getOpCount();
+            for (int i = 0; i < opCount; i++) {
+                final HistoricalOp op = featureOps.getOpAt(i);
+                writeHistoricalOpDLocked(op, serializer);
+            }
+            serializer.endTag(null, TAG_FEATURE);
+        }
+
         private void writeHistoricalOpDLocked(@NonNull HistoricalOp op,
                 @NonNull XmlSerializer serializer) throws IOException {
             final LongSparseArray keys = op.collectKeys();
@@ -1648,24 +1718,31 @@
         private final @NonNull String mOpsPrefix;
         private final @NonNull String mUidPrefix;
         private final @NonNull String mPackagePrefix;
+        private final @NonNull String mFeaturePrefix;
         private final @NonNull String mEntryPrefix;
         private final @NonNull String mUidStatePrefix;
         private final @NonNull PrintWriter mWriter;
         private final int mFilterUid;
         private final String mFilterPackage;
+        private final String mFilterFeatureId;
         private final int mFilterOp;
+        private final @HistoricalOpsRequestFilter int mFilter;
 
-        StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer,
-                int filterUid, String filterPackage, int filterOp) {
+        StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer, int filterUid,
+                @Nullable String filterPackage, @Nullable String filterFeatureId, int filterOp,
+                @HistoricalOpsRequestFilter int filter) {
             mOpsPrefix = prefix + "  ";
             mUidPrefix = mOpsPrefix + "  ";
             mPackagePrefix = mUidPrefix + "  ";
-            mEntryPrefix = mPackagePrefix + "  ";
+            mFeaturePrefix = mPackagePrefix + "  ";
+            mEntryPrefix = mFeaturePrefix + "  ";
             mUidStatePrefix = mEntryPrefix + "  ";
             mWriter = writer;
             mFilterUid = filterUid;
             mFilterPackage = filterPackage;
+            mFilterFeatureId = filterFeatureId;
             mFilterOp = filterOp;
+            mFilter = filter;
         }
 
         @Override
@@ -1691,7 +1768,7 @@
 
         @Override
         public void visitHistoricalUidOps(HistoricalUidOps ops) {
-            if (mFilterUid != Process.INVALID_UID && mFilterUid != ops.getUid()) {
+            if ((mFilter & FILTER_BY_UID) != 0 && mFilterUid != ops.getUid()) {
                 return;
             }
             mWriter.println();
@@ -1703,7 +1780,8 @@
 
         @Override
         public void visitHistoricalPackageOps(HistoricalPackageOps ops) {
-            if (mFilterPackage != null && !mFilterPackage.equals(ops.getPackageName())) {
+            if ((mFilter & FILTER_BY_PACKAGE_NAME) != 0 && !mFilterPackage.equals(
+                    ops.getPackageName())) {
                 return;
             }
             mWriter.print(mPackagePrefix);
@@ -1713,8 +1791,20 @@
         }
 
         @Override
+        public void visitHistoricalFeatureOps(HistoricalFeatureOps ops) {
+            if ((mFilter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(mFilterPackage,
+                    ops.getFeatureId())) {
+                return;
+            }
+            mWriter.print(mFeaturePrefix);
+            mWriter.print("Feature ");
+            mWriter.print(ops.getFeatureId());
+            mWriter.println(":");
+        }
+
+        @Override
         public void visitHistoricalOp(HistoricalOp ops) {
-            if (mFilterOp != AppOpsManager.OP_NONE && mFilterOp != ops.getOpCode()) {
+            if ((mFilter & FILTER_BY_OP_NAMES) != 0  && mFilterOp != ops.getOpCode()) {
                 return;
             }
             mWriter.print(mEntryPrefix);
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index cf83dd6..f15d999 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -17,8 +17,10 @@
 package com.android.server.compat;
 
 import android.compat.Compatibility.ChangeConfig;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.os.Environment;
+import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.LongArray;
 import android.util.LongSparseArray;
@@ -26,8 +28,11 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.AndroidBuildClassifier;
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.IOverrideValidator;
+import com.android.internal.compat.OverrideAllowedState;
 import com.android.server.compat.config.Change;
 import com.android.server.compat.config.XmlParser;
 
@@ -54,22 +59,14 @@
 
     private static final String TAG = "CompatConfig";
 
-    private static final CompatConfig sInstance = new CompatConfig().initConfigFromLib(
-            Environment.buildPath(
-                    Environment.getRootDirectory(), "etc", "compatconfig"));
-
     @GuardedBy("mChanges")
     private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
 
-    @VisibleForTesting
-    CompatConfig() {
-    }
+    private IOverrideValidator mOverrideValidator;
 
-    /**
-     * @return The static instance of this class to be used within the system server.
-     */
-    static CompatConfig get() {
-        return sInstance;
+    @VisibleForTesting
+    CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) {
+        mOverrideValidator = new OverrideValidatorImpl(androidBuildClassifier, context, this);
     }
 
     /**
@@ -159,8 +156,12 @@
      * @param enabled     If the change should be enabled or disabled.
      * @return {@code true} if the change existed before adding the override.
      */
-    boolean addOverride(long changeId, String packageName, boolean enabled) {
+    boolean addOverride(long changeId, String packageName, boolean enabled)
+            throws RemoteException, SecurityException {
         boolean alreadyKnown = true;
+        OverrideAllowedState allowedState =
+                mOverrideValidator.getOverrideAllowedState(changeId, packageName);
+        allowedState.enforce(changeId, packageName);
         synchronized (mChanges) {
             CompatChange c = mChanges.get(changeId);
             if (c == null) {
@@ -186,6 +187,20 @@
     }
 
     /**
+     * Returns the minimum sdk version for which this change should be enabled (or 0 if it is not
+     * target sdk gated).
+     */
+    int minTargetSdkForChangeId(long changeId) {
+        synchronized (mChanges) {
+            CompatChange c = mChanges.get(changeId);
+            if (c == null) {
+                return 0;
+            }
+            return c.getEnableAfterTargetSdk();
+        }
+    }
+
+    /**
      * Removes an override previously added via {@link #addOverride(long, String, boolean)}. This
      * restores the default behaviour for the given change and app, once any app processes have been
      * restarted.
@@ -194,34 +209,44 @@
      * @param packageName The app package name that was overridden.
      * @return {@code true} if an override existed;
      */
-    boolean removeOverride(long changeId, String packageName) {
+    boolean removeOverride(long changeId, String packageName)
+            throws RemoteException, SecurityException {
         boolean overrideExists = false;
         synchronized (mChanges) {
             CompatChange c = mChanges.get(changeId);
-            if (c != null) {
-                overrideExists = true;
-                c.removePackageOverride(packageName);
+            try {
+                if (c != null) {
+                    OverrideAllowedState allowedState =
+                            mOverrideValidator.getOverrideAllowedState(changeId, packageName);
+                    allowedState.enforce(changeId, packageName);
+                    overrideExists = true;
+                    c.removePackageOverride(packageName);
+                }
+            } catch (RemoteException e) {
+                // Should never occur, since validator is in the same process.
+                throw new RuntimeException("Unable to call override validator!", e);
             }
         }
         return overrideExists;
     }
 
     /**
-     * Overrides the enabled state for a given change and app. This method is intended to be used
-     * *only* for debugging purposes.
+     * Overrides the enabled state for a given change and app.
      *
      * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
      *
      * @param overrides   list of overrides to default changes config.
      * @param packageName app for which the overrides will be applied.
      */
-    void addOverrides(CompatibilityChangeConfig overrides, String packageName) {
+    void addOverrides(CompatibilityChangeConfig overrides, String packageName)
+            throws RemoteException, SecurityException {
         synchronized (mChanges) {
             for (Long changeId : overrides.enabledChanges()) {
                 addOverride(changeId, packageName, true);
             }
             for (Long changeId : overrides.disabledChanges()) {
                 addOverride(changeId, packageName, false);
+
             }
         }
     }
@@ -235,10 +260,22 @@
      *
      * @param packageName The package for which the overrides should be purged.
      */
-    void removePackageOverrides(String packageName) {
+    void removePackageOverrides(String packageName) throws RemoteException, SecurityException {
         synchronized (mChanges) {
             for (int i = 0; i < mChanges.size(); ++i) {
-                mChanges.valueAt(i).removePackageOverride(packageName);
+                try {
+                    CompatChange change = mChanges.valueAt(i);
+                    OverrideAllowedState allowedState =
+                            mOverrideValidator.getOverrideAllowedState(change.getId(),
+                                                                       packageName);
+                    allowedState.enforce(change.getId(), packageName);
+                    if (change != null) {
+                        mChanges.valueAt(i).removePackageOverride(packageName);
+                    }
+                } catch (RemoteException e) {
+                    // Should never occur, since validator is in the same process.
+                    throw new RuntimeException("Unable to call override validator!", e);
+                }
             }
         }
     }
@@ -326,17 +363,23 @@
         }
     }
 
-    CompatConfig initConfigFromLib(File libraryDir) {
+    static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) {
+        CompatConfig config = new CompatConfig(androidBuildClassifier, context);
+        config.initConfigFromLib(Environment.buildPath(
+                Environment.getRootDirectory(), "etc", "compatconfig"));
+        return config;
+    }
+
+    void initConfigFromLib(File libraryDir) {
         if (!libraryDir.exists() || !libraryDir.isDirectory()) {
             Slog.e(TAG, "No directory " + libraryDir + ", skipping");
-            return this;
+            return;
         }
         for (File f : libraryDir.listFiles()) {
             Slog.d(TAG, "Found a config file: " + f.getPath());
             //TODO(b/138222363): Handle duplicate ids across config files.
             readConfig(f);
         }
-        return this;
     }
 
     private void readConfig(File configFile) {
@@ -350,4 +393,7 @@
         }
     }
 
+    IOverrideValidator getOverrideValidator() {
+        return mOverrideValidator;
+    }
 }
diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
new file mode 100644
index 0000000..dfc0080
--- /dev/null
+++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2019 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.compat;
+
+import static com.android.internal.compat.OverrideAllowedState.ALLOWED;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH;
+import static com.android.internal.compat.OverrideAllowedState.PACKAGE_DOES_NOT_EXIST;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.AndroidBuildClassifier;
+import com.android.internal.compat.IOverrideValidator;
+import com.android.internal.compat.OverrideAllowedState;
+
+/**
+ * Implementation of the policy for allowing compat change overrides.
+ */
+public class OverrideValidatorImpl extends IOverrideValidator.Stub {
+
+    private AndroidBuildClassifier mAndroidBuildClassifier;
+    private Context mContext;
+    private CompatConfig mCompatConfig;
+
+    @VisibleForTesting
+    OverrideValidatorImpl(AndroidBuildClassifier androidBuildClassifier,
+                          Context context, CompatConfig config) {
+        mAndroidBuildClassifier = androidBuildClassifier;
+        mContext = context;
+        mCompatConfig = config;
+    }
+
+    @Override
+    public OverrideAllowedState getOverrideAllowedState(long changeId, String packageName) {
+        boolean debuggableBuild = false;
+        boolean finalBuild = false;
+
+        debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild();
+        finalBuild = mAndroidBuildClassifier.isFinalBuild();
+
+        // Allow any override for userdebug or eng builds.
+        if (debuggableBuild) {
+            return new OverrideAllowedState(ALLOWED, -1, -1);
+        }
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager == null) {
+            throw new IllegalStateException("No PackageManager!");
+        }
+        ApplicationInfo applicationInfo;
+        try {
+            applicationInfo = packageManager.getApplicationInfo(packageName, 0);
+        } catch (NameNotFoundException e) {
+            return new OverrideAllowedState(PACKAGE_DOES_NOT_EXIST, -1, -1);
+        }
+        int appTargetSdk = applicationInfo.targetSdkVersion;
+        // Only allow overriding debuggable apps.
+        if ((applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+            return new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1);
+        }
+        int minTargetSdk = mCompatConfig.minTargetSdkForChangeId(changeId);
+        // Do not allow overriding non-target sdk gated changes on user builds
+        if (minTargetSdk == -1) {
+            return new OverrideAllowedState(DISABLED_NON_TARGET_SDK, appTargetSdk, minTargetSdk);
+        }
+        // Allow overriding any change for debuggable apps on non-final builds.
+        if (!finalBuild) {
+            return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk);
+        }
+        // Only allow to opt-in for a targetSdk gated change.
+        if (applicationInfo.targetSdkVersion < minTargetSdk) {
+            return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk);
+        }
+        return new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, appTargetSdk, minTargetSdk);
+    }
+}
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 6ec4b9f..bb8b12e 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -27,9 +27,12 @@
 import android.util.Slog;
 import android.util.StatsLog;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.AndroidBuildClassifier;
 import com.android.internal.compat.ChangeReporter;
 import com.android.internal.compat.CompatibilityChangeConfig;
 import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.IOverrideValidator;
 import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
@@ -46,11 +49,21 @@
 
     private final Context mContext;
     private final ChangeReporter mChangeReporter;
+    private final CompatConfig mCompatConfig;
 
     public PlatformCompat(Context context) {
         mContext = context;
         mChangeReporter = new ChangeReporter(
                 StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
+        mCompatConfig = CompatConfig.create(new AndroidBuildClassifier(), mContext);
+    }
+
+    @VisibleForTesting
+    PlatformCompat(Context context, CompatConfig compatConfig) {
+        mContext = context;
+        mChangeReporter = new ChangeReporter(
+                StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
+        mCompatConfig = compatConfig;
     }
 
     @Override
@@ -75,7 +88,7 @@
 
     @Override
     public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
-        if (CompatConfig.get().isChangeEnabled(changeId, appInfo)) {
+        if (mCompatConfig.isChangeEnabled(changeId, appInfo)) {
             reportChange(changeId, appInfo.uid,
                     StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
             return true;
@@ -122,57 +135,59 @@
      * otherwise.
      */
     public boolean registerListener(long changeId, CompatChange.ChangeListener listener) {
-        return CompatConfig.get().registerListener(changeId, listener);
+        return mCompatConfig.registerListener(changeId, listener);
     }
 
     @Override
-    public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
-        CompatConfig.get().addOverrides(overrides, packageName);
+    public void setOverrides(CompatibilityChangeConfig overrides, String packageName)
+            throws RemoteException, SecurityException {
+        mCompatConfig.addOverrides(overrides, packageName);
         killPackage(packageName);
     }
 
     @Override
-    public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) {
-        CompatConfig.get().addOverrides(overrides, packageName);
+    public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName)
+            throws RemoteException, SecurityException {
+        mCompatConfig.addOverrides(overrides, packageName);
     }
 
     @Override
-    public void clearOverrides(String packageName) {
-        CompatConfig config = CompatConfig.get();
-        config.removePackageOverrides(packageName);
+    public void clearOverrides(String packageName) throws RemoteException, SecurityException {
+        mCompatConfig.removePackageOverrides(packageName);
         killPackage(packageName);
     }
 
     @Override
-    public void clearOverridesForTest(String packageName) {
-        CompatConfig config = CompatConfig.get();
-        config.removePackageOverrides(packageName);
+    public void clearOverridesForTest(String packageName)
+            throws RemoteException, SecurityException {
+        mCompatConfig.removePackageOverrides(packageName);
     }
 
     @Override
-    public boolean clearOverride(long changeId, String packageName) {
-        boolean existed = CompatConfig.get().removeOverride(changeId, packageName);
+    public boolean clearOverride(long changeId, String packageName)
+            throws RemoteException, SecurityException {
+        boolean existed = mCompatConfig.removeOverride(changeId, packageName);
         killPackage(packageName);
         return existed;
     }
 
     @Override
     public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) {
-        return CompatConfig.get().getAppConfig(appInfo);
+        return mCompatConfig.getAppConfig(appInfo);
     }
 
     @Override
     public CompatibilityChangeInfo[] listAllChanges() {
-        return CompatConfig.get().dumpChanges();
+        return mCompatConfig.dumpChanges();
     }
 
     /**
      * Check whether the change is known to the compat config.
-     * @param changeId
+     *
      * @return {@code true} if the change is known.
      */
     public boolean isKnownChangeId(long changeId) {
-        return CompatConfig.get().isKnownChangeId(changeId);
+        return mCompatConfig.isKnownChangeId(changeId);
 
     }
 
@@ -182,11 +197,11 @@
      *
      * @param appInfo The app in question
      * @return A sorted long array of change IDs. We use a primitive array to minimize memory
-     *      footprint: Every app process will store this array statically so we aim to reduce
-     *      overhead as much as possible.
+     * footprint: Every app process will store this array statically so we aim to reduce
+     * overhead as much as possible.
      */
     public long[] getDisabledChanges(ApplicationInfo appInfo) {
-        return CompatConfig.get().getDisabledChanges(appInfo);
+        return mCompatConfig.getDisabledChanges(appInfo);
     }
 
     /**
@@ -196,18 +211,24 @@
      * @return The change ID, or {@code -1} if no change with that name exists.
      */
     public long lookupChangeId(String name) {
-        return CompatConfig.get().lookupChangeId(name);
+        return mCompatConfig.lookupChangeId(name);
     }
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
-        CompatConfig.get().dumpConfig(pw);
+        mCompatConfig.dumpConfig(pw);
+    }
+
+    @Override
+    public IOverrideValidator getOverrideValidator() {
+        return mCompatConfig.getOverrideValidator();
     }
 
     /**
      * Clears information stored about events reported on behalf of an app.
      * To be called once upon app start or end. A second call would be a no-op.
+     *
      * @param appInfo the app to reset
      */
     public void resetReporting(ApplicationInfo appInfo) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e39d6d5..d7ae5b5 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -1305,13 +1305,21 @@
     }
 
     private SurfaceControl.ScreenshotGraphicBuffer screenshotInternal(int displayId) {
-        final IBinder token = getDisplayToken(displayId);
-        if (token == null) {
-            return null;
+        synchronized (mSyncRoot) {
+            final IBinder token = getDisplayToken(displayId);
+            if (token == null) {
+                return null;
+            }
+            final LogicalDisplay logicalDisplay = mLogicalDisplays.get(displayId);
+            if (logicalDisplay == null) {
+                return null;
+            }
+
+            final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked();
+            return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(),
+                    displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(),
+                    false /* useIdentityTransform */, 0 /* rotation */);
         }
-        return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(
-                        token, new Rect(), 0 /* width */, 0 /* height */,
-                        false /* useIdentityTransform */, 0 /* rotation */);
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
index f90fab4..31d4816 100644
--- a/services/core/java/com/android/server/integrity/IntegrityFileManager.java
+++ b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
@@ -25,6 +25,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.integrity.model.RuleMetadata;
 import com.android.server.integrity.parser.RuleBinaryParser;
+import com.android.server.integrity.parser.RuleIndexRange;
 import com.android.server.integrity.parser.RuleIndexingController;
 import com.android.server.integrity.parser.RuleMetadataParser;
 import com.android.server.integrity.parser.RuleParseException;
@@ -152,7 +153,7 @@
             throws IOException, RuleParseException {
         synchronized (RULES_LOCK) {
             // Try to identify indexes from the index file.
-            List<List<Integer>> ruleReadingIndexes =
+            List<RuleIndexRange> ruleReadingIndexes =
                     mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata);
 
             // Read the rules based on the index information.
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
new file mode 100644
index 0000000..8c8450e
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 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.integrity.parser;
+
+import android.annotation.Nullable;
+
+/**
+ * A wrapper class to represent an indexing range that is identified by the {@link
+ * RuleIndexingController}.
+ */
+public class RuleIndexRange {
+    private static int sStartIndex;
+    private static int sEndIndex;
+
+    /** Constructor with start and end indexes. */
+    public RuleIndexRange(int startIndex, int endIndex) {
+        this.sStartIndex = startIndex;
+        this.sEndIndex = endIndex;
+    }
+
+    /** Returns the startIndex. */
+    public int getStartIndex() {
+        return sStartIndex;
+    }
+
+    /** Returns the end index. */
+    public int getEndIndex() {
+        return sEndIndex;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object object) {
+        return sStartIndex == ((RuleIndexRange) object).getStartIndex()
+                && sEndIndex == ((RuleIndexRange) object).getEndIndex();
+    }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
index b642fa6..c971322 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
@@ -17,6 +17,7 @@
 package com.android.server.integrity.parser;
 
 import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
+import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
 import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue;
 import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue;
 
@@ -24,25 +25,27 @@
 
 import com.android.server.integrity.model.BitInputStream;
 
-import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
 
 /** Helper class to identify the necessary indexes that needs to be read. */
 public class RuleIndexingController {
 
-    private static TreeMap<String, Integer> sPackageNameBasedIndexes;
-    private static TreeMap<String, Integer> sAppCertificateBasedIndexes;
-    private static TreeMap<String, Integer> sUnindexedRuleIndexes;
+    private static LinkedHashMap<String, Integer> sPackageNameBasedIndexes;
+    private static LinkedHashMap<String, Integer> sAppCertificateBasedIndexes;
+    private static LinkedHashMap<String, Integer> sUnindexedRuleIndexes;
 
     /**
      * Provide the indexing file to read and the object will be constructed by reading and
      * identifying the indexes.
      */
-    public RuleIndexingController(FileInputStream fileInputStream) throws IOException {
-        BitInputStream bitInputStream = new BitInputStream(fileInputStream);
+    public RuleIndexingController(InputStream inputStream) throws IOException {
+        BitInputStream bitInputStream = new BitInputStream(inputStream);
         sPackageNameBasedIndexes = getNextIndexGroup(bitInputStream);
         sAppCertificateBasedIndexes = getNextIndexGroup(bitInputStream);
         sUnindexedRuleIndexes = getNextIndexGroup(bitInputStream);
@@ -52,24 +55,57 @@
      * Returns a list of integers with the starting and ending bytes of the rules that needs to be
      * read and evaluated.
      */
-    public List<List<Integer>> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) {
-        // TODO(b/145493956): Identify and return the indexes that needs to be read.
-        return new ArrayList<>();
+    public List<RuleIndexRange> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) {
+        ArrayList<RuleIndexRange> indexRanges = new ArrayList();
+
+        // Add the range for package name indexes rules.
+        indexRanges.add(
+                searchIndexingKeysRangeContainingKey(
+                        sPackageNameBasedIndexes, appInstallMetadata.getPackageName()));
+
+        // Add the range for app certificate indexes rules.
+        indexRanges.add(
+                searchIndexingKeysRangeContainingKey(
+                        sAppCertificateBasedIndexes, appInstallMetadata.getAppCertificate()));
+
+        // Add the range for unindexed rules.
+        indexRanges.add(
+                new RuleIndexRange(
+                        sUnindexedRuleIndexes.get(START_INDEXING_KEY),
+                        sUnindexedRuleIndexes.get(END_INDEXING_KEY)));
+
+        return indexRanges;
     }
 
-    private TreeMap<String, Integer> getNextIndexGroup(BitInputStream bitInputStream)
+    private LinkedHashMap<String, Integer> getNextIndexGroup(BitInputStream bitInputStream)
             throws IOException {
-        TreeMap<String, Integer> keyToIndexMap = new TreeMap<>();
+        LinkedHashMap<String, Integer> keyToIndexMap = new LinkedHashMap<>();
         while (bitInputStream.hasNext()) {
             String key = getStringValue(bitInputStream);
             int value = getIntValue(bitInputStream);
 
             keyToIndexMap.put(key, value);
 
-            if (key == END_INDEXING_KEY) {
+            if (key.matches(END_INDEXING_KEY)) {
                 break;
             }
         }
         return keyToIndexMap;
     }
+
+    private RuleIndexRange searchIndexingKeysRangeContainingKey(
+            LinkedHashMap<String, Integer> indexMap, String searchedKey) {
+        TreeSet<String> keyTreeSet =
+                indexMap.keySet().stream()
+                        .filter(key -> !key.matches(START_INDEXING_KEY) && !key.matches(
+                                END_INDEXING_KEY))
+                        .collect(Collectors.toCollection(TreeSet::new));
+
+        String minIndex = keyTreeSet.floor(searchedKey);
+        String maxIndex = keyTreeSet.ceiling(searchedKey);
+
+        return new RuleIndexRange(
+                indexMap.get(minIndex == null ? START_INDEXING_KEY : minIndex),
+                indexMap.get(maxIndex == null ? END_INDEXING_KEY : maxIndex));
+    }
 }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 4546ea4..7233f34 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.READ_CONTACTS;
 import static android.content.Context.KEYGUARD_SERVICE;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_ALL;
 
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
@@ -29,6 +30,7 @@
 import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
 import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.internal.widget.LockPatternUtils.USER_FRP;
 import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
 import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
@@ -118,6 +120,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.RebootEscrowListener;
 import com.android.internal.widget.VerifyCredentialResponse;
 import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
@@ -222,7 +225,10 @@
 
     private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
 
+    private final RebootEscrowManager mRebootEscrowManager;
+
     private boolean mFirstCallToVold;
+
     // Current password metric for all users on the device. Updated when user unlocks
     // the device or changes password. Removed when user is stopped.
     @GuardedBy("this")
@@ -483,6 +489,11 @@
                     new PasswordSlotManager());
         }
 
+        public RebootEscrowManager getRebootEscrowManager(RebootEscrowManager.Callbacks callbacks,
+                LockSettingsStorage storage) {
+            return new RebootEscrowManager(mContext, callbacks, storage);
+        }
+
         public boolean hasEnrolledBiometrics(int userId) {
             BiometricManager bm = mContext.getSystemService(BiometricManager.class);
             return bm.hasEnrolledBiometrics(userId);
@@ -550,6 +561,9 @@
 
         mSpManager = injector.getSyntheticPasswordManager(mStorage);
 
+        mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
+                mStorage);
+
         LocalServices.addService(LockSettingsInternal.class, new LocalService());
     }
 
@@ -782,7 +796,14 @@
         migrateOldData();
         getGateKeeperService();
         mSpManager.initWeaverService();
-        // Find the AuthSecret HAL
+        getAuthSecretHal();
+        mDeviceProvisionedObserver.onSystemReady();
+        mRebootEscrowManager.loadRebootEscrowDataIfAvailable();
+        // TODO: maybe skip this for split system user mode.
+        mStorage.prefetchUser(UserHandle.USER_SYSTEM);
+    }
+
+    private void getAuthSecretHal() {
         try {
             mAuthSecretService = IAuthSecret.getService();
         } catch (NoSuchElementException e) {
@@ -790,9 +811,6 @@
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to get AuthSecret HAL", e);
         }
-        mDeviceProvisionedObserver.onSystemReady();
-        // TODO: maybe skip this for split system user mode.
-        mStorage.prefetchUser(UserHandle.USER_SYSTEM);
     }
 
     private void migrateOldData() {
@@ -2466,10 +2484,18 @@
 
     private void onAuthTokenKnownForUser(@UserIdInt int userId, AuthenticationToken auth) {
         if (mInjector.isGsiRunning()) {
-            Slog.w(TAG, "AuthSecret disabled in GSI");
+            Slog.w(TAG, "Running in GSI; skipping calls to AuthSecret and RebootEscrow");
             return;
         }
 
+        mRebootEscrowManager.callToRebootEscrowIfNeeded(userId, auth.getVersion(),
+                auth.getSyntheticPassword());
+
+        callToAuthSecretIfNeeded(userId, auth);
+    }
+
+    private void callToAuthSecretIfNeeded(@UserIdInt int userId,
+            AuthenticationToken auth) {
         // Pass the primary user's auth secret to the HAL
         if (mAuthSecretService != null && mUserManager.getUserInfo(userId).isPrimary()) {
             try {
@@ -3288,5 +3314,46 @@
             return LockSettingsService.this.getUserPasswordMetrics(userHandle);
         }
 
+        @Override
+        public void prepareRebootEscrow() {
+            if (!mRebootEscrowManager.prepareRebootEscrow()) {
+                return;
+            }
+            mStrongAuth.requireStrongAuth(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE, USER_ALL);
+        }
+
+        @Override
+        public void setRebootEscrowListener(RebootEscrowListener listener) {
+            mRebootEscrowManager.setRebootEscrowListener(listener);
+        }
+
+        @Override
+        public void clearRebootEscrow() {
+            if (!mRebootEscrowManager.clearRebootEscrow()) {
+                return;
+            }
+            mStrongAuth.noLongerRequireStrongAuth(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE,
+                    USER_ALL);
+        }
+
+        @Override
+        public boolean armRebootEscrow() {
+            return mRebootEscrowManager.armRebootEscrowIfNeeded();
+        }
+    }
+
+    private class RebootEscrowCallbacks implements RebootEscrowManager.Callbacks {
+        @Override
+        public boolean isUserSecure(int userId) {
+            return LockSettingsService.this.isUserSecure(userId);
+        }
+
+        @Override
+        public void onRebootEscrowRestored(byte spVersion, byte[] syntheticPassword, int userId) {
+            SyntheticPasswordManager.AuthenticationToken
+                    authToken = new SyntheticPasswordManager.AuthenticationToken(spVersion);
+            authToken.recreateDirectly(syntheticPassword);
+            onCredentialVerified(authToken, CHALLENGE_NONE, 0, null, userId);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 3dab3ce..fec0189 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -81,6 +81,8 @@
     private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
     private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
 
+    private static final String REBOOT_ESCROW_FILE = "reboot.escrow.key";
+
     private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
 
     private static final Object DEFAULT = new Object();
@@ -261,6 +263,22 @@
         return hasFile(getChildProfileLockFile(userId));
     }
 
+    public void writeRebootEscrow(int userId, byte[] rebootEscrow) {
+        writeFile(getRebootEscrowFile(userId), rebootEscrow);
+    }
+
+    public byte[] readRebootEscrow(int userId) {
+        return readFile(getRebootEscrowFile(userId));
+    }
+
+    public boolean hasRebootEscrow(int userId) {
+        return hasFile(getRebootEscrowFile(userId));
+    }
+
+    public void removeRebootEscrow(int userId) {
+        deleteFile(getRebootEscrowFile(userId));
+    }
+
     public boolean hasPassword(int userId) {
         return hasFile(getLockPasswordFilename(userId));
     }
@@ -384,6 +402,11 @@
         return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
     }
 
+    @VisibleForTesting
+    String getRebootEscrowFile(int userId) {
+        return getLockCredentialFilePathForUser(userId, REBOOT_ESCROW_FILE);
+    }
+
     private String getLockCredentialFilePathForUser(int userId, String basename) {
         String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() +
                         SYSTEM_DIRECTORY;
@@ -479,18 +502,10 @@
         if (parentInfo == null) {
             // This user owns its lock settings files - safe to delete them
             synchronized (mFileWriteLock) {
-                String name = getLockPasswordFilename(userId);
-                File file = new File(name);
-                if (file.exists()) {
-                    file.delete();
-                    mCache.putFile(name, null);
-                }
-                name = getLockPatternFilename(userId);
-                file = new File(name);
-                if (file.exists()) {
-                    file.delete();
-                    mCache.putFile(name, null);
-                }
+                deleteFilesAndRemoveCache(
+                        getLockPasswordFilename(userId),
+                        getLockPatternFilename(userId),
+                        getRebootEscrowFile(userId));
             }
         } else {
             // Managed profile
@@ -512,6 +527,16 @@
         }
     }
 
+    private void deleteFilesAndRemoveCache(String... names) {
+        for (String name : names) {
+            File file = new File(name);
+            if (file.exists()) {
+                file.delete();
+                mCache.putFile(name, null);
+            }
+        }
+    }
+
     @VisibleForTesting
     void closeDatabase() {
         mOpenHelper.close();
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
index a84306c..91cf53e 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -16,10 +16,8 @@
 
 package com.android.server.locksettings;
 
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker
-        .STRONG_AUTH_NOT_REQUIRED;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker
-        .STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
 
 import android.app.AlarmManager;
 import android.app.AlarmManager.OnAlarmListener;
@@ -50,6 +48,7 @@
     private static final int MSG_UNREGISTER_TRACKER = 3;
     private static final int MSG_REMOVE_USER = 4;
     private static final int MSG_SCHEDULE_STRONG_AUTH_TIMEOUT = 5;
+    private static final int MSG_NO_LONGER_REQUIRE_STRONG_AUTH = 6;
 
     private static final String STRONG_AUTH_TIMEOUT_ALARM_TAG =
             "LockSettingsStrongAuth.timeoutForUser";
@@ -109,6 +108,26 @@
         }
     }
 
+    private void handleNoLongerRequireStrongAuth(int strongAuthReason, int userId) {
+        if (userId == UserHandle.USER_ALL) {
+            for (int i = 0; i < mStrongAuthForUser.size(); i++) {
+                int key = mStrongAuthForUser.keyAt(i);
+                handleNoLongerRequireStrongAuthOneUser(strongAuthReason, key);
+            }
+        } else {
+            handleNoLongerRequireStrongAuthOneUser(strongAuthReason, userId);
+        }
+    }
+
+    private void handleNoLongerRequireStrongAuthOneUser(int strongAuthReason, int userId) {
+        int oldValue = mStrongAuthForUser.get(userId, mDefaultStrongAuthFlags);
+        int newValue = oldValue & ~strongAuthReason;
+        if (oldValue != newValue) {
+            mStrongAuthForUser.put(userId, newValue);
+            notifyStrongAuthTrackers(newValue, userId);
+        }
+    }
+
     private void handleRemoveUser(int userId) {
         int index = mStrongAuthForUser.indexOfKey(userId);
         if (index >= 0) {
@@ -174,6 +193,16 @@
         }
     }
 
+    void noLongerRequireStrongAuth(int strongAuthReason, int userId) {
+        if (userId == UserHandle.USER_ALL || userId >= UserHandle.USER_SYSTEM) {
+            mHandler.obtainMessage(MSG_NO_LONGER_REQUIRE_STRONG_AUTH, strongAuthReason,
+                    userId).sendToTarget();
+        } else {
+            throw new IllegalArgumentException(
+                    "userId must be an explicit user id or USER_ALL");
+        }
+    }
+
     public void reportUnlock(int userId) {
         requireStrongAuth(STRONG_AUTH_NOT_REQUIRED, userId);
     }
@@ -216,6 +245,9 @@
                 case MSG_SCHEDULE_STRONG_AUTH_TIMEOUT:
                     handleScheduleStrongAuthTimeout(msg.arg1);
                     break;
+                case MSG_NO_LONGER_REQUIRE_STRONG_AUTH:
+                    handleNoLongerRequireStrongAuth(msg.arg1, msg.arg2);
+                    break;
             }
         }
     };
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowData.java b/services/core/java/com/android/server/locksettings/RebootEscrowData.java
new file mode 100644
index 0000000..aee608e
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowData.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2019 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.locksettings;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Holds the data necessary to complete a reboot escrow of the Synthetic Password.
+ */
+class RebootEscrowData {
+    /**
+     * This is the current version of the escrow data format. This should be incremented if the
+     * format on disk is changed.
+     */
+    private static final int CURRENT_VERSION = 1;
+
+    /** The secret key will be of this format. */
+    private static final String KEY_ALGO = "AES";
+
+    /** The key size used for encrypting the reboot escrow data. */
+    private static final int KEY_SIZE_BITS = 256;
+
+    /** The algorithm used for the encryption of the key blob. */
+    private static final String CIPHER_ALGO = "AES/GCM/NoPadding";
+
+    private RebootEscrowData(byte spVersion, byte[] iv, byte[] syntheticPassword, byte[] blob,
+            byte[] key) {
+        mSpVersion = spVersion;
+        mIv = iv;
+        mSyntheticPassword = syntheticPassword;
+        mBlob = blob;
+        mKey = key;
+    }
+
+    private final byte mSpVersion;
+    private final byte[] mIv;
+    private final byte[] mSyntheticPassword;
+    private final byte[] mBlob;
+    private final byte[] mKey;
+
+    public byte getSpVersion() {
+        return mSpVersion;
+    }
+
+    public byte[] getIv() {
+        return mIv;
+    }
+
+    public byte[] getSyntheticPassword() {
+        return mSyntheticPassword;
+    }
+
+    public byte[] getBlob() {
+        return mBlob;
+    }
+
+    public byte[] getKey() {
+        return mKey;
+    }
+
+    static SecretKeySpec fromKeyBytes(byte[] keyBytes) {
+        return new SecretKeySpec(keyBytes, KEY_ALGO);
+    }
+
+    static RebootEscrowData fromEncryptedData(SecretKeySpec keySpec, byte[] blob)
+            throws IOException {
+        Preconditions.checkNotNull(keySpec);
+        Preconditions.checkNotNull(blob);
+
+        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob));
+        int version = dis.readInt();
+        if (version != CURRENT_VERSION) {
+            throw new IOException("Unsupported version " + version);
+        }
+
+        byte spVersion = dis.readByte();
+
+        int ivSize = dis.readInt();
+        if (ivSize < 0 || ivSize > 32) {
+            throw new IOException("IV out of range: " + ivSize);
+        }
+        byte[] iv = new byte[ivSize];
+        dis.readFully(iv);
+
+        int cipherTextSize = dis.readInt();
+        if (cipherTextSize < 0) {
+            throw new IOException("Invalid cipher text size: " + cipherTextSize);
+        }
+
+        byte[] cipherText = new byte[cipherTextSize];
+        dis.readFully(cipherText);
+
+        final byte[] syntheticPassword;
+        try {
+            Cipher c = Cipher.getInstance(CIPHER_ALGO);
+            c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
+            syntheticPassword = c.doFinal(cipherText);
+        } catch (NoSuchAlgorithmException | InvalidKeyException | BadPaddingException
+                | IllegalBlockSizeException | NoSuchPaddingException
+                | InvalidAlgorithmParameterException e) {
+            throw new IOException("Could not decrypt ciphertext", e);
+        }
+
+        return new RebootEscrowData(spVersion, iv, syntheticPassword, blob, keySpec.getEncoded());
+    }
+
+    static RebootEscrowData fromSyntheticPassword(byte spVersion, byte[] syntheticPassword)
+            throws IOException {
+        Preconditions.checkNotNull(syntheticPassword);
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        DataOutputStream dos = new DataOutputStream(bos);
+
+        final SecretKey secretKey;
+        try {
+            KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGO);
+            keyGenerator.init(KEY_SIZE_BITS, new SecureRandom());
+            secretKey = keyGenerator.generateKey();
+        } catch (NoSuchAlgorithmException e) {
+            throw new IOException("Could not generate new secret key", e);
+        }
+
+        final byte[] cipherText;
+        final byte[] iv;
+        try {
+            Cipher cipher = Cipher.getInstance(CIPHER_ALGO);
+            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+            cipherText = cipher.doFinal(syntheticPassword);
+            iv = cipher.getIV();
+        } catch (NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException
+                | NoSuchPaddingException | InvalidKeyException e) {
+            throw new IOException("Could not encrypt reboot escrow data", e);
+        }
+
+        dos.writeInt(CURRENT_VERSION);
+        dos.writeByte(spVersion);
+        dos.writeInt(iv.length);
+        dos.write(iv);
+        dos.writeInt(cipherText.length);
+        dos.write(cipherText);
+
+        return new RebootEscrowData(spVersion, iv, syntheticPassword, bos.toByteArray(),
+                secretKey.getEncoded());
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
new file mode 100644
index 0000000..d2e54f9
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2020 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.locksettings;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.rebootescrow.IRebootEscrow;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.RebootEscrowListener;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.crypto.spec.SecretKeySpec;
+
+class RebootEscrowManager {
+    private static final String TAG = "RebootEscrowManager";
+
+    /**
+     * Used to track when the reboot escrow is wanted. Set to false when mRebootEscrowReady is
+     * true.
+     */
+    private final AtomicBoolean mRebootEscrowWanted = new AtomicBoolean(false);
+
+    /** Used to track when reboot escrow is ready. */
+    private boolean mRebootEscrowReady;
+
+    /** Notified when mRebootEscrowReady changes. */
+    private RebootEscrowListener mRebootEscrowListener;
+
+    /**
+     * Stores the reboot escrow data between when it's supplied and when
+     * {@link #armRebootEscrowIfNeeded()} is called.
+     */
+    private RebootEscrowData mPendingRebootEscrowData;
+
+    private final UserManager mUserManager;
+
+    private final Injector mInjector;
+
+    private final LockSettingsStorage mStorage;
+
+    private final Callbacks mCallbacks;
+
+    interface Callbacks {
+        boolean isUserSecure(int userId);
+        void onRebootEscrowRestored(byte spVersion, byte[] syntheticPassword, int userId);
+    }
+
+    static class Injector {
+        protected Context mContext;
+
+        Injector(Context context) {
+            mContext = context;
+        }
+
+        public Context getContext() {
+            return mContext;
+        }
+        public UserManager getUserManager() {
+            return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        }
+
+        public @Nullable IRebootEscrow getRebootEscrow() {
+            try {
+                return IRebootEscrow.Stub.asInterface(ServiceManager.getService(
+                        "android.hardware.rebootescrow.IRebootEscrow/default"));
+            } catch (NoSuchElementException e) {
+                Slog.i(TAG, "Device doesn't implement RebootEscrow HAL");
+            }
+            return null;
+        }
+    }
+
+    RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) {
+        this(new Injector(context), callbacks, storage);
+    }
+
+    @VisibleForTesting
+    RebootEscrowManager(Injector injector, Callbacks callbacks,
+            LockSettingsStorage storage) {
+        mInjector = injector;
+        mCallbacks = callbacks;
+        mStorage = storage;
+        mUserManager = injector.getUserManager();
+    }
+
+    void loadRebootEscrowDataIfAvailable() {
+        IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+        if (rebootEscrow == null) {
+            return;
+        }
+
+        final SecretKeySpec escrowKey;
+        try {
+            byte[] escrowKeyBytes = rebootEscrow.retrieveKey();
+            if (escrowKeyBytes == null) {
+                return;
+            } else if (escrowKeyBytes.length != 32) {
+                Slog.e(TAG, "IRebootEscrow returned key of incorrect size "
+                        + escrowKeyBytes.length);
+                return;
+            }
+
+            // Make sure we didn't get the null key.
+            int zero = 0;
+            for (int i = 0; i < escrowKeyBytes.length; i++) {
+                zero |= escrowKeyBytes[i];
+            }
+            if (zero == 0) {
+                Slog.w(TAG, "IRebootEscrow returned an all-zeroes key");
+                return;
+            }
+
+            // Overwrite the existing key with the null key
+            rebootEscrow.storeKey(new byte[32]);
+
+            escrowKey = RebootEscrowData.fromKeyBytes(escrowKeyBytes);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Could not retrieve escrow data");
+            return;
+        }
+
+        List<UserInfo> users = mUserManager.getUsers();
+        for (UserInfo user : users) {
+            if (mCallbacks.isUserSecure(user.id)) {
+                restoreRebootEscrowForUser(user.id, escrowKey);
+            }
+        }
+    }
+
+    private void restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) {
+        if (!mStorage.hasRebootEscrow(userId)) {
+            return;
+        }
+
+        try {
+            byte[] blob = mStorage.readRebootEscrow(userId);
+            mStorage.removeRebootEscrow(userId);
+
+            RebootEscrowData escrowData = RebootEscrowData.fromEncryptedData(escrowKey, blob);
+
+            mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(),
+                    escrowData.getSyntheticPassword(), userId);
+        } catch (IOException e) {
+            Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e);
+        }
+    }
+
+    void callToRebootEscrowIfNeeded(@UserIdInt int userId, byte spVersion,
+            byte[] syntheticPassword) {
+        if (!mRebootEscrowWanted.compareAndSet(true, false)) {
+            return;
+        }
+
+        IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+        if (rebootEscrow == null) {
+            setRebootEscrowReady(false);
+            return;
+        }
+
+        final RebootEscrowData escrowData;
+        try {
+            escrowData = RebootEscrowData.fromSyntheticPassword(spVersion, syntheticPassword);
+        } catch (IOException e) {
+            setRebootEscrowReady(false);
+            Slog.w(TAG, "Could not escrow reboot data", e);
+            return;
+        }
+
+        mPendingRebootEscrowData = escrowData;
+        mStorage.writeRebootEscrow(userId, escrowData.getBlob());
+
+        setRebootEscrowReady(true);
+    }
+
+    private void clearRebootEscrowIfNeeded() {
+        mRebootEscrowWanted.set(false);
+        setRebootEscrowReady(false);
+
+        IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+        if (rebootEscrow == null) {
+            return;
+        }
+
+        try {
+            rebootEscrow.storeKey(new byte[32]);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Could not call RebootEscrow HAL to shred key");
+        }
+
+        List<UserInfo> users = mUserManager.getUsers();
+        for (UserInfo user : users) {
+            mStorage.removeRebootEscrow(user.id);
+        }
+    }
+
+    boolean armRebootEscrowIfNeeded() {
+        if (!mRebootEscrowReady) {
+            return false;
+        }
+
+        IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+        if (rebootEscrow == null) {
+            return false;
+        }
+
+        RebootEscrowData escrowData = mPendingRebootEscrowData;
+        if (escrowData == null) {
+            return false;
+        }
+
+        boolean armedRebootEscrow = false;
+        try {
+            rebootEscrow.storeKey(escrowData.getKey());
+            armedRebootEscrow = true;
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed escrow secret to RebootEscrow HAL", e);
+        }
+        return armedRebootEscrow;
+    }
+
+    private void setRebootEscrowReady(boolean ready) {
+        if (mRebootEscrowReady != ready) {
+            mRebootEscrowListener.onPreparedForReboot(ready);
+        }
+        mRebootEscrowReady = ready;
+    }
+
+    boolean prepareRebootEscrow() {
+        if (mInjector.getRebootEscrow() == null) {
+            return false;
+        }
+
+        clearRebootEscrowIfNeeded();
+        mRebootEscrowWanted.set(true);
+        return true;
+    }
+
+    boolean clearRebootEscrow() {
+        if (mInjector.getRebootEscrow() == null) {
+            return false;
+        }
+
+        clearRebootEscrowIfNeeded();
+        return true;
+    }
+
+    void setRebootEscrowListener(RebootEscrowListener listener) {
+        mRebootEscrowListener = listener;
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 0fe16be..b726e57 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -38,7 +38,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
 import com.android.internal.widget.ICheckCredentialProgressCallback;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockscreenCredential;
@@ -285,6 +284,14 @@
         public byte[] getSyntheticPassword() {
             return mSyntheticPassword;
         }
+
+        /**
+         * Returns the version of this AuthenticationToken for use with reconstructing
+         * this with a synthetic password version.
+         */
+        public byte getVersion() {
+            return mVersion;
+        }
     }
 
     static class PasswordData {
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 115155c..55c4e21 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -20,7 +20,6 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Intent;
-import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.RouteSessionInfo;
 
@@ -50,13 +49,13 @@
             String controlCategory, long requestId);
     public abstract void releaseSession(int sessionId);
 
-    public abstract void selectRoute(int sessionId, MediaRoute2Info route);
-    public abstract void deselectRoute(int sessionId, MediaRoute2Info route);
-    public abstract void transferToRoute(int sessionId, MediaRoute2Info route);
+    public abstract void selectRoute(int sessionId, String routeId);
+    public abstract void deselectRoute(int sessionId, String routeId);
+    public abstract void transferToRoute(int sessionId, String routeId);
 
-    public abstract void sendControlRequest(MediaRoute2Info route, Intent request);
-    public abstract void requestSetVolume(MediaRoute2Info route, int volume);
-    public abstract void requestUpdateVolume(MediaRoute2Info route, int delta);
+    public abstract void sendControlRequest(String routeId, Intent request);
+    public abstract void requestSetVolume(String routeId, int volume);
+    public abstract void requestUpdateVolume(String routeId, int delta);
 
     @NonNull
     public String getUniqueId() {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index f8d8f9f..28bb034 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -24,7 +24,6 @@
 import android.content.ServiceConnection;
 import android.media.IMediaRoute2Provider;
 import android.media.IMediaRoute2ProviderClient;
-import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
 import android.media.RouteSessionInfo;
@@ -95,46 +94,46 @@
     }
 
     @Override
-    public void selectRoute(int sessionId, MediaRoute2Info route) {
+    public void selectRoute(int sessionId, String routeId) {
         if (mConnectionReady) {
-            mActiveConnection.selectRoute(sessionId, route.getId());
+            mActiveConnection.selectRoute(sessionId, routeId);
         }
     }
 
     @Override
-    public void deselectRoute(int sessionId, MediaRoute2Info route) {
+    public void deselectRoute(int sessionId, String routeId) {
         if (mConnectionReady) {
-            mActiveConnection.deselectRoute(sessionId, route.getId());
+            mActiveConnection.deselectRoute(sessionId, routeId);
         }
     }
 
     @Override
-    public void transferToRoute(int sessionId, MediaRoute2Info route) {
+    public void transferToRoute(int sessionId, String routeId) {
         if (mConnectionReady) {
-            mActiveConnection.transferToRoute(sessionId, route.getId());
+            mActiveConnection.transferToRoute(sessionId, routeId);
         }
     }
 
     @Override
-    public void sendControlRequest(MediaRoute2Info route, Intent request) {
+    public void sendControlRequest(String routeId, Intent request) {
         if (mConnectionReady) {
-            mActiveConnection.sendControlRequest(route.getId(), request);
+            mActiveConnection.sendControlRequest(routeId, request);
             updateBinding();
         }
     }
 
     @Override
-    public void requestSetVolume(MediaRoute2Info route, int volume) {
+    public void requestSetVolume(String routeId, int volume) {
         if (mConnectionReady) {
-            mActiveConnection.requestSetVolume(route.getId(), volume);
+            mActiveConnection.requestSetVolume(routeId, volume);
             updateBinding();
         }
     }
 
     @Override
-    public void requestUpdateVolume(MediaRoute2Info route, int delta) {
+    public void requestUpdateVolume(String routeId, int delta) {
         if (mConnectionReady) {
-            mActiveConnection.requestUpdateVolume(route.getId(), delta);
+            mActiveConnection.requestUpdateVolume(routeId, delta);
             updateBinding();
         }
     }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 22ba1bf..a5ffbb8 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -79,7 +79,6 @@
     @GuardedBy("mLock")
     private int mCurrentUserId = -1;
 
-
     MediaRouter2ServiceImpl(Context context) {
         mContext = context;
     }
@@ -89,17 +88,23 @@
         final int uid = Binder.getCallingUid();
         final int userId = UserHandle.getUserId(uid);
 
-        Collection<MediaRoute2Info> systemRoutes;
-        synchronized (mLock) {
-            UserRecord userRecord = mUserRecords.get(userId);
-            if (userRecord == null) {
-                userRecord = new UserRecord(userId);
-                mUserRecords.put(userId, userRecord);
-                initializeUserLocked(userRecord);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            Collection<MediaRoute2Info> systemRoutes;
+            synchronized (mLock) {
+                UserRecord userRecord = getOrCreateUserRecordLocked(userId);
+                MediaRoute2ProviderInfo providerInfo =
+                        userRecord.mHandler.mSystemProvider.getProviderInfo();
+                if (providerInfo != null) {
+                    systemRoutes = providerInfo.getRoutes();
+                } else {
+                    systemRoutes = Collections.emptyList();
+                }
             }
-            systemRoutes = userRecord.mHandler.mSystemProvider.getProviderInfo().getRoutes();
+            return new ArrayList<>(systemRoutes);
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
-        return new ArrayList<>(systemRoutes);
     }
 
     public void registerClient(@NonNull IMediaRouter2Client client,
@@ -400,12 +405,7 @@
             int uid, int pid, String packageName, int userId, boolean trusted) {
         final IBinder binder = client.asBinder();
         if (mAllClientRecords.get(binder) == null) {
-            UserRecord userRecord = mUserRecords.get(userId);
-            if (userRecord == null) {
-                userRecord = new UserRecord(userId);
-                mUserRecords.put(userId, userRecord);
-                initializeUserLocked(userRecord);
-            }
+            UserRecord userRecord = getOrCreateUserRecordLocked(userId);
             Client2Record clientRecord = new Client2Record(userRecord, client, uid, pid,
                     packageName, trusted);
             try {
@@ -556,12 +556,7 @@
         final IBinder binder = manager.asBinder();
         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
         if (managerRecord == null) {
-            boolean newUser = false;
-            UserRecord userRecord = mUserRecords.get(userId);
-            if (userRecord == null) {
-                userRecord = new UserRecord(userId);
-                newUser = true;
-            }
+            UserRecord userRecord = getOrCreateUserRecordLocked(userId);
             managerRecord = new ManagerRecord(userRecord, manager, uid, pid, packageName, trusted);
             try {
                 binder.linkToDeath(managerRecord, 0);
@@ -569,11 +564,6 @@
                 throw new RuntimeException("Media router manager died prematurely.", ex);
             }
 
-            if (newUser) {
-                mUserRecords.put(userId, userRecord);
-                initializeUserLocked(userRecord);
-            }
-
             userRecord.mManagerRecords.add(managerRecord);
             mAllManagerRecords.put(binder, managerRecord);
 
@@ -661,14 +651,17 @@
         return sessionInfos;
     }
 
-    private void initializeUserLocked(UserRecord userRecord) {
-        if (DEBUG) {
-            Slog.d(TAG, userRecord + ": Initialized");
+    private UserRecord getOrCreateUserRecordLocked(int userId) {
+        UserRecord userRecord = mUserRecords.get(userId);
+        if (userRecord == null) {
+            userRecord = new UserRecord(userId);
+            mUserRecords.put(userId, userRecord);
+            if (userId == mCurrentUserId) {
+                userRecord.mHandler.sendMessage(
+                        obtainMessage(UserHandler::start, userRecord.mHandler));
+            }
         }
-        if (userRecord.mUserId == mCurrentUserId) {
-            userRecord.mHandler.sendMessage(
-                    obtainMessage(UserHandler::start, userRecord.mHandler));
-        }
+        return userRecord;
     }
 
     private void disposeUserIfNeededLocked(UserRecord userRecord) {
@@ -990,7 +983,7 @@
                     clientRecord, route, controlCategory, requestId);
             mSessionCreationRequests.add(request);
 
-            provider.requestCreateSession(clientRecord.mPackageName, route.getId(),
+            provider.requestCreateSession(clientRecord.mPackageName, route.getOriginalId(),
                     controlCategory, requestId);
         }
 
@@ -1007,7 +1000,8 @@
             if (provider == null) {
                 return;
             }
-            provider.selectRoute(RouteSessionInfo.getSessionId(uniqueSessionId), route);
+            provider.selectRoute(RouteSessionInfo.getSessionId(uniqueSessionId),
+                    route.getOriginalId());
         }
 
         private void deselectRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -1023,7 +1017,8 @@
             if (provider == null) {
                 return;
             }
-            provider.deselectRoute(RouteSessionInfo.getSessionId(uniqueSessionId), route);
+            provider.deselectRoute(RouteSessionInfo.getSessionId(uniqueSessionId),
+                    route.getOriginalId());
         }
 
         private void transferToRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -1039,7 +1034,8 @@
             if (provider == null) {
                 return;
             }
-            provider.transferToRoute(RouteSessionInfo.getSessionId(uniqueSessionId), route);
+            provider.transferToRoute(RouteSessionInfo.getSessionId(uniqueSessionId),
+                    route.getOriginalId());
         }
 
         private boolean checkArgumentsForSessionControl(@NonNull Client2Record clientRecord,
@@ -1246,21 +1242,21 @@
         private void sendControlRequest(MediaRoute2Info route, Intent request) {
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
             if (provider != null) {
-                provider.sendControlRequest(route, request);
+                provider.sendControlRequest(route.getOriginalId(), request);
             }
         }
 
         private void requestSetVolume(MediaRoute2Info route, int volume) {
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
             if (provider != null) {
-                provider.requestSetVolume(route, volume);
+                provider.requestSetVolume(route.getOriginalId(), volume);
             }
         }
 
         private void requestUpdateVolume(MediaRoute2Info route, int delta) {
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
             if (provider != null) {
-                provider.requestUpdateVolume(route, delta);
+                provider.requestUpdateVolume(route.getOriginalId(), delta);
             }
         }
 
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 8fdfcbf..5302765 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -102,33 +102,33 @@
     }
 
     @Override
-    public void selectRoute(int sessionId, MediaRoute2Info route) {
+    public void selectRoute(int sessionId, String routeId) {
         //TODO: implement method
     }
 
     @Override
-    public void deselectRoute(int sessionId, MediaRoute2Info route) {
+    public void deselectRoute(int sessionId, String routeId) {
         //TODO: implement method
     }
 
     @Override
-    public void transferToRoute(int sessionId, MediaRoute2Info route) {
+    public void transferToRoute(int sessionId, String routeId) {
         //TODO: implement method
     }
 
     //TODO: implement method
     @Override
-    public void sendControlRequest(@NonNull MediaRoute2Info route, @NonNull Intent request) {
+    public void sendControlRequest(@NonNull String routeId, @NonNull Intent request) {
     }
 
     //TODO: implement method
     @Override
-    public void requestSetVolume(MediaRoute2Info route, int volume) {
+    public void requestSetVolume(String routeId, int volume) {
     }
 
     //TODO: implement method
     @Override
-    public void requestUpdateVolume(MediaRoute2Info route, int delta) {
+    public void requestUpdateVolume(String routeId, int delta) {
     }
 
     void initializeRoutes() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f42f4f7..a4f4d07 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -236,7 +236,6 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.util.function.TriPredicate;
 import com.android.server.DeviceIdleInternal;
@@ -1182,21 +1181,14 @@
 
         @Override
         public void onNotificationBubbleChanged(String key, boolean isBubble) {
-            String pkg;
-            synchronized (mNotificationLock) {
-                NotificationRecord r = mNotificationsByKey.get(key);
-                pkg = r != null && r.sbn != null ? r.sbn.getPackageName() : null;
-            }
-            boolean isAppForeground = pkg != null
-                    && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
             synchronized (mNotificationLock) {
                 NotificationRecord r = mNotificationsByKey.get(key);
                 if (r != null) {
                     final StatusBarNotification n = r.sbn;
                     final int callingUid = n.getUid();
-                    pkg = n.getPackageName();
+                    final String pkg = n.getPackageName();
                     if (isBubble && isNotificationAppropriateToBubble(r, pkg, callingUid,
-                            null /* oldEntry */, isAppForeground)) {
+                            null /* oldEntry */)) {
                         r.getNotification().flags |= FLAG_BUBBLE;
                     } else {
                         r.getNotification().flags &= ~FLAG_BUBBLE;
@@ -5386,7 +5378,7 @@
     private void flagNotificationForBubbles(NotificationRecord r, String pkg, int userId,
             NotificationRecord oldRecord, boolean isAppForeground) {
         Notification notification = r.getNotification();
-        if (isNotificationAppropriateToBubble(r, pkg, userId, oldRecord, isAppForeground)) {
+        if (isNotificationAppropriateToBubble(r, pkg, userId, oldRecord)) {
             notification.flags |= FLAG_BUBBLE;
         } else {
             notification.flags &= ~FLAG_BUBBLE;
@@ -5406,7 +5398,7 @@
      * accounting for user choice & policy.
      */
     private boolean isNotificationAppropriateToBubble(NotificationRecord r, String pkg, int userId,
-            NotificationRecord oldRecord, boolean isAppForeground) {
+            NotificationRecord oldRecord) {
         Notification notification = r.getNotification();
         if (!canBubble(r, pkg, userId)) {
             // no log: canBubble has its own
@@ -5418,11 +5410,6 @@
             return false;
         }
 
-        if (isAppForeground) {
-            // If the app is foreground it always gets to bubble
-            return true;
-        }
-
         if (oldRecord != null && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0) {
             // This is an update to an active bubble
             return true;
@@ -5438,7 +5425,7 @@
         boolean isMessageStyle = Notification.MessagingStyle.class.equals(
                 notification.getNotificationStyle());
         if (!isMessageStyle && (peopleList == null || peopleList.isEmpty())) {
-            logBubbleError(r.getKey(), "if not foreground, must have a person and be "
+            logBubbleError(r.getKey(), "Must have a person and be "
                     + "Notification.MessageStyle or Notification.CATEGORY_CALL");
             return false;
         }
@@ -5446,6 +5433,11 @@
         // Communication is a message or a call
         boolean isCall = CATEGORY_CALL.equals(notification.category);
         boolean hasForegroundService = (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
+        if (hasForegroundService && !isCall) {
+            logBubbleError(r.getKey(),
+                    "foreground services must be Notification.CATEGORY_CALL to bubble");
+            return false;
+        }
         if (isMessageStyle) {
             if (hasValidRemoteInput(notification)) {
                 return true;
@@ -5459,7 +5451,7 @@
             logBubbleError(r.getKey(), "calls require foreground service");
             return false;
         }
-        logBubbleError(r.getKey(), "if not foreground, must be "
+        logBubbleError(r.getKey(), "Must be "
                 + "Notification.MessageStyle or Notification.CATEGORY_CALL");
         return false;
     }
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index f1947ac..b782ca96 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -1154,6 +1154,10 @@
                 throws RemoteException, IOException {
             PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0,
                     userId);
+            if (packageInfo == null) {
+                throw new IOException("Unable to get target package");
+            }
+
             String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
 
             ApkAssets apkAssets = null;
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java b/services/core/java/com/android/server/people/PeopleServiceInternal.java
similarity index 67%
rename from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
rename to services/core/java/com/android/server/people/PeopleServiceInternal.java
index 155a6d2..31d30362 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
+++ b/services/core/java/com/android/server/people/PeopleServiceInternal.java
@@ -14,17 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dagger.qualifiers;
+package com.android.server.people;
 
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import android.service.appprediction.IPredictionService;
 
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Qualifier;
-
-@Qualifier
-@Documented
-@Retention(RUNTIME)
-public @interface DisplayId {
-}
+/**
+ * @hide Only for use within the system server.
+ */
+public abstract class PeopleServiceInternal extends IPredictionService.Stub {}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index c868953..426cd01 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -119,7 +119,6 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.dex.DexManager;
@@ -1684,7 +1683,7 @@
                 computeProgressLocked(true);
 
                 // Unpack native libraries for non-incremental installation
-                if (isIncrementalInstallation()) {
+                if (!isIncrementalInstallation()) {
                     extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs());
                 }
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 04e7372..6bd9c48 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -13452,9 +13452,7 @@
 
             // Okay!
             targetPackageSetting.setInstallerPackageName(installerPackageName);
-            if (installerPackageName != null) {
-                mSettings.mInstallerPackages.add(installerPackageName);
-            }
+            mSettings.addInstallerPackageNames(targetPackageSetting.installSource);
             scheduleWriteSettingsLocked();
         }
     }
@@ -15160,7 +15158,8 @@
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
 
         final String pkgName = pkg.getPackageName();
-        final String installerPackageName = installArgs.installSource.installerPackageName;
+        final InstallSource installSource = installArgs.installSource;
+        final String installerPackageName = installSource.installerPackageName;
         final int[] installedForUsers = res.origUsers;
         final int installReason = installArgs.installReason;
 
@@ -15171,7 +15170,7 @@
             // For system-bundled packages, we assume that installing an upgraded version
             // of the package implies that the user actually wants to run that new code,
             // so we enable the package.
-            PackageSetting ps = mSettings.mPackages.get(pkgName);
+            final PackageSetting ps = mSettings.mPackages.get(pkgName);
             final int userId = installArgs.user.getIdentifier();
             if (ps != null) {
                 if (isSystemApp(pkg)) {
@@ -15208,8 +15207,8 @@
                     ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
                 }
 
-                ps.setInstallSource(installArgs.installSource);
-
+                ps.setInstallSource(installSource);
+                mSettings.addInstallerPackageNames(installSource);
 
                 // When replacing an existing package, preserve the original install reason for all
                 // users that had the package installed before.
@@ -15239,7 +15238,6 @@
             res.name = pkgName;
             res.uid = pkg.getUid();
             res.pkg = pkg;
-            mSettings.setInstallerPackageName(pkgName, installerPackageName);
             res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
             //to update install status
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "writeSettings");
@@ -23057,6 +23055,11 @@
         }
 
         @Override
+        public void setDeviceOwnerProtectedPackages(List<String> packageNames) {
+            mProtectedPackages.setDeviceOwnerProtectedPackages(packageNames);
+        }
+
+        @Override
         public boolean isPackageDataProtected(int userId, String packageName) {
             return mProtectedPackages.isPackageDataProtected(userId, packageName);
         }
diff --git a/services/core/java/com/android/server/pm/ProtectedPackages.java b/services/core/java/com/android/server/pm/ProtectedPackages.java
index 231168e..4da3cc3 100644
--- a/services/core/java/com/android/server/pm/ProtectedPackages.java
+++ b/services/core/java/com/android/server/pm/ProtectedPackages.java
@@ -24,6 +24,10 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Manages package names that need special protection.
@@ -49,6 +53,10 @@
     @GuardedBy("this")
     private final String mDeviceProvisioningPackage;
 
+    @Nullable
+    @GuardedBy("this")
+    private List<String> mDeviceOwnerProtectedPackages;
+
     private final Context mContext;
 
     public ProtectedPackages(Context context) {
@@ -70,6 +78,10 @@
                 : profileOwnerPackages.clone();
     }
 
+    public synchronized void setDeviceOwnerProtectedPackages(List<String> packageNames) {
+        mDeviceOwnerProtectedPackages = new ArrayList<String>(packageNames);
+    }
+
     private synchronized boolean hasDeviceOwnerOrProfileOwner(int userId, String packageName) {
         if (packageName == null) {
             return false;
@@ -105,7 +117,8 @@
      * can modify its data or package state.
      */
     private synchronized boolean isProtectedPackage(String packageName) {
-        return packageName != null && packageName.equals(mDeviceProvisioningPackage);
+        return packageName != null && (packageName.equals(mDeviceProvisioningPackage)
+                || ArrayUtils.contains(mDeviceOwnerProtectedPackages, packageName));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 9642a1a..f9a3361 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -280,8 +280,11 @@
     /** Map from package name to settings */
     final ArrayMap<String, PackageSetting> mPackages = new ArrayMap<>();
 
-    /** List of packages that installed other packages */
-    final ArraySet<String> mInstallerPackages = new ArraySet<>();
+    /**
+     * List of packages that were involved in installing other packages, i.e. are listed
+     * in at least one app's InstallSource.
+     */
+    private final ArraySet<String> mInstallerPackages = new ArraySet<>();
 
     /** Map from package name to appId and excluded userids */
     private final ArrayMap<String, KernelPackageState> mKernelMapping = new ArrayMap<>();
@@ -441,16 +444,6 @@
         return mPermissions.canPropagatePermissionToInstantApp(permName);
     }
 
-    void setInstallerPackageName(String pkgName, String installerPkgName) {
-        PackageSetting p = mPackages.get(pkgName);
-        if (p != null) {
-            p.setInstallerPackageName(installerPkgName);
-            if (installerPkgName != null) {
-                mInstallerPackages.add(installerPkgName);
-            }
-        }
-    }
-
     /** Gets and optionally creates a new shared user id. */
     SharedUserSetting getSharedUserLPw(String name, int pkgFlags, int pkgPrivateFlags,
             boolean create) throws PackageManagerException {
@@ -3777,9 +3770,10 @@
         }
         if (packageSetting != null) {
             packageSetting.uidError = "true".equals(uidError);
-            packageSetting.installSource = InstallSource.create(
+            InstallSource installSource = InstallSource.create(
                     installInitiatingPackageName, installOriginatingPackageName,
                     installerPackageName, "true".equals(isOrphaned));
+            packageSetting.installSource = installSource;
             packageSetting.volumeUuid = volumeUuid;
             packageSetting.categoryHint = categoryHint;
             packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr;
@@ -3809,9 +3803,7 @@
                 packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0, null);
             }
 
-            if (installerPackageName != null) {
-                mInstallerPackages.add(installerPackageName);
-            }
+            addInstallerPackageNames(installSource);
 
             int outerDepth = parser.getDepth();
             int type;
@@ -3870,6 +3862,18 @@
         }
     }
 
+    void addInstallerPackageNames(InstallSource installSource) {
+        if (installSource.installerPackageName != null) {
+            mInstallerPackages.add(installSource.installerPackageName);
+        }
+        if (installSource.initiatingPackageName != null) {
+            mInstallerPackages.add(installSource.initiatingPackageName);
+        }
+        if (installSource.originatingPackageName != null) {
+            mInstallerPackages.add(installSource.originatingPackageName);
+        }
+    }
+
     private void readDisabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser,
             int userId) throws IOException, XmlPullParserException {
         int outerDepth = parser.getDepth();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 1455d4a..e5d5b57 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1132,14 +1132,13 @@
     }
 
     /**
-     * Returns the user type, e.g. {@link UserManager#USER_TYPE_FULL_GUEST}, of the given userId,
-     * or null if the user doesn't exist.
+     * Returns whether the given user (specified by userId) is of the given user type, such as
+     * {@link UserManager#USER_TYPE_FULL_GUEST}.
      */
     @Override
-    public @Nullable String getUserTypeForUser(@UserIdInt int userId) {
-        // TODO(b/142482943): Decide on the appropriate permission requirements.
-        checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserTypeForUser");
-        return getUserTypeNoChecks(userId);
+    public boolean isUserOfType(@UserIdInt int userId, String userType) {
+        checkManageUsersPermission("check user type");
+        return userType != null && userType.equals(getUserTypeNoChecks(userId));
     }
 
     /**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5a124a7..e04d10c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -697,7 +697,7 @@
                     accessibilityShortcutActivated();
                     break;
                 case MSG_BUGREPORT_TV:
-                    requestFullBugreport();
+                    requestFullBugreportOrLaunchHandlerApp();
                     break;
                 case MSG_ACCESSIBILITY_TV:
                     if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable(false)) {
@@ -3061,12 +3061,14 @@
         return mAccessibilityTvScheduled;
     }
 
-    private void requestFullBugreport() {
+    private void requestFullBugreportOrLaunchHandlerApp() {
         if ("1".equals(SystemProperties.get("ro.debuggable"))
                 || Settings.Global.getInt(mContext.getContentResolver(),
                         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1) {
             try {
-                ActivityManager.getService().requestFullBugReport();
+                if (!ActivityManager.getService().launchBugReportHandlerApp()) {
+                    ActivityManager.getService().requestFullBugReport();
+                }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error taking bugreport", e);
             }
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index fdb14be..c36d5ef 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -17,8 +17,10 @@
 package com.android.server.recoverysystem;
 
 import android.content.Context;
+import android.content.IntentSender;
 import android.net.LocalSocket;
 import android.net.LocalSocketAddress;
+import android.os.Binder;
 import android.os.IRecoverySystem;
 import android.os.IRecoverySystemProgressListener;
 import android.os.PowerManager;
@@ -28,6 +30,9 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.RebootEscrowListener;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
 import libcore.io.IoUtils;
@@ -45,7 +50,7 @@
  * triggers /system/bin/uncrypt via init to de-encrypt an OTA package on the
  * /data partition so that it can be accessed under the recovery image.
  */
-public class RecoverySystemService extends IRecoverySystem.Stub {
+public class RecoverySystemService extends IRecoverySystem.Stub implements RebootEscrowListener {
     private static final String TAG = "RecoverySystemService";
     private static final boolean DEBUG = false;
 
@@ -67,6 +72,10 @@
     private final Injector mInjector;
     private final Context mContext;
 
+    private boolean mPreparedForReboot;
+    private String mUnattendedRebootToken;
+    private IntentSender mPreparedForRebootIntentSender;
+
     static class Injector {
         protected final Context mContext;
 
@@ -78,6 +87,10 @@
             return mContext;
         }
 
+        public LockSettingsInternal getLockSettingsService() {
+            return LocalServices.getService(LockSettingsInternal.class);
+        }
+
         public PowerManager getPowerManager() {
             return (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         }
@@ -120,14 +133,23 @@
      * Handles the lifecycle events for the RecoverySystemService.
      */
     public static final class Lifecycle extends SystemService {
+        private RecoverySystemService mRecoverySystemService;
+
         public Lifecycle(Context context) {
             super(context);
         }
 
         @Override
+        public void onBootPhase(int phase) {
+            if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+                mRecoverySystemService.onSystemServicesReady();
+            }
+        }
+
+        @Override
         public void onStart() {
-            RecoverySystemService recoverySystemService = new RecoverySystemService(getContext());
-            publishBinderService(Context.RECOVERY_SERVICE, recoverySystemService);
+            mRecoverySystemService = new RecoverySystemService(getContext());
+            publishBinderService(Context.RECOVERY_SERVICE, mRecoverySystemService);
         }
     }
 
@@ -141,6 +163,11 @@
         mContext = injector.getContext();
     }
 
+    @VisibleForTesting
+    void onSystemServicesReady() {
+        mInjector.getLockSettingsService().setRebootEscrowListener(this);
+    }
+
     @Override // Binder call
     public boolean uncrypt(String filename, IRecoverySystemProgressListener listener) {
         if (DEBUG) Slog.d(TAG, "uncrypt: " + filename);
@@ -255,6 +282,95 @@
         }
     }
 
+    @Override // Binder call
+    public boolean requestLskf(String updateToken, IntentSender intentSender) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+
+        if (updateToken == null) {
+            return false;
+        }
+
+        // No need to prepare again for the same token.
+        if (mPreparedForReboot && updateToken.equals(mUnattendedRebootToken)) {
+            return true;
+        }
+
+        mPreparedForReboot = false;
+        mUnattendedRebootToken = updateToken;
+        mPreparedForRebootIntentSender = intentSender;
+
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            mInjector.getLockSettingsService().prepareRebootEscrow();
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        return true;
+    }
+
+    @Override
+    public void onPreparedForReboot(boolean ready) {
+        if (mUnattendedRebootToken == null) {
+            Slog.w(TAG, "onPreparedForReboot called when mUnattendedRebootToken is null");
+        }
+
+        mPreparedForReboot = ready;
+        if (ready) {
+            sendPreparedForRebootIntentIfNeeded();
+        }
+    }
+
+    private void sendPreparedForRebootIntentIfNeeded() {
+        final IntentSender intentSender = mPreparedForRebootIntentSender;
+        if (intentSender != null) {
+            try {
+                intentSender.sendIntent(null, 0, null, null, null);
+            } catch (IntentSender.SendIntentException e) {
+                Slog.w(TAG, "Could not send intent for prepared reboot: " + e.getMessage());
+            }
+        }
+    }
+
+    @Override // Binder call
+    public boolean clearLskf() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+
+        mPreparedForReboot = false;
+        mUnattendedRebootToken = null;
+        mPreparedForRebootIntentSender = null;
+
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            mInjector.getLockSettingsService().clearRebootEscrow();
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        return true;
+    }
+
+    @Override // Binder call
+    public boolean rebootWithLskf(String updateToken, String reason) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+
+        if (!mPreparedForReboot) {
+            return false;
+        }
+
+        if (updateToken != null && updateToken.equals(mUnattendedRebootToken)) {
+            if (!mInjector.getLockSettingsService().armRebootEscrow()) {
+                return false;
+            }
+
+            PowerManager pm = mInjector.getPowerManager();
+            pm.reboot(reason);
+            return true;
+        }
+
+        return false;
+    }
+
     /**
      * Check if any of the init services is still running. If so, we cannot
      * start a new uncrypt/setup-bcb/clear-bcb service right away; otherwise
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 9b22f33..0b89646 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -40,7 +40,6 @@
 import android.os.HidlMemoryUtil;
 
 import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Utilities for type conversion between SoundTrigger HAL types and SoundTriggerMiddleware service
@@ -60,7 +59,8 @@
         aidlProperties.maxSoundModels = hidlProperties.maxSoundModels;
         aidlProperties.maxKeyPhrases = hidlProperties.maxKeyPhrases;
         aidlProperties.maxUsers = hidlProperties.maxUsers;
-        aidlProperties.recognitionModes = hidlProperties.recognitionModes;
+        aidlProperties.recognitionModes =
+                hidl2aidlRecognitionModes(hidlProperties.recognitionModes);
         aidlProperties.captureTransition = hidlProperties.captureTransition;
         aidlProperties.maxBufferMs = hidlProperties.maxBufferMs;
         aidlProperties.concurrentCapture = hidlProperties.concurrentCapture;
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index b4f4eca..f661b5e 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -22,7 +22,7 @@
 import android.app.timedetector.NetworkTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.Intent;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import java.io.PrintWriter;
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 02656ea..da848d8 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -24,10 +24,10 @@
 import android.app.timedetector.NetworkTimeSuggestion;
 import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.Intent;
+import android.os.TimestampedValue;
 import android.telephony.TelephonyManager;
 import android.util.LocalLog;
 import android.util.Slog;
-import android.util.TimestampedValue;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
diff --git a/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java b/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
new file mode 100644
index 0000000..7fe4bf8
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
@@ -0,0 +1,802 @@
+/*
+ * Copyright (C) 2019 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.utils.quota;
+
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import static com.android.server.utils.quota.Uptc.string;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.LongArrayQueue;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.util.quota.CountQuotaTrackerProto;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Class that tracks whether an app has exceeded its defined count quota.
+ *
+ * Quotas are applied per userId-package-tag combination (UPTC). Tags can be null.
+ *
+ * This tracker tracks the count of instantaneous events.
+ *
+ * Limits are applied according to the category the UPTC is placed in. If a UPTC reaches its limit,
+ * it will be considered out of quota until it is below that limit again. A {@link Category} is a
+ * basic construct to apply different limits to different groups of UPTCs. For example, standby
+ * buckets can be a set of categories, or foreground & background could be two categories. If every
+ * UPTC should have the same limits applied, then only one category is needed
+ * ({@see Category.SINGLE_CATEGORY}).
+ *
+ * Note: all limits are enforced per category unless explicitly stated otherwise.
+ *
+ * Test: atest com.android.server.utils.quota.CountQuotaTrackerTest
+ *
+ * @hide
+ */
+public class CountQuotaTracker extends QuotaTracker {
+    private static final String TAG = CountQuotaTracker.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final String ALARM_TAG_CLEANUP = "*" + TAG + ".cleanup*";
+
+    @VisibleForTesting
+    static class ExecutionStats {
+        /**
+         * The time after which this record should be considered invalid (out of date), in the
+         * elapsed realtime timebase.
+         */
+        public long expirationTimeElapsed;
+
+        /** The window size that's used when counting the number of events. */
+        public long windowSizeMs;
+        /** The maximum number of events allowed within the window size. */
+        public int countLimit;
+
+        /** The total number of events that occurred in the window. */
+        public int countInWindow;
+
+        /**
+         * The time after which the app will be under the category quota again. This is only valid
+         * if {@link #countInWindow} >= {@link #countLimit}.
+         */
+        public long inQuotaTimeElapsed;
+
+        @Override
+        public String toString() {
+            return "expirationTime=" + expirationTimeElapsed + ", "
+                    + "windowSizeMs=" + windowSizeMs + ", "
+                    + "countLimit=" + countLimit + ", "
+                    + "countInWindow=" + countInWindow + ", "
+                    + "inQuotaTime=" + inQuotaTimeElapsed;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof ExecutionStats) {
+                ExecutionStats other = (ExecutionStats) obj;
+                return this.expirationTimeElapsed == other.expirationTimeElapsed
+                        && this.windowSizeMs == other.windowSizeMs
+                        && this.countLimit == other.countLimit
+                        && this.countInWindow == other.countInWindow
+                        && this.inQuotaTimeElapsed == other.inQuotaTimeElapsed;
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 0;
+            result = 31 * result + Long.hashCode(expirationTimeElapsed);
+            result = 31 * result + Long.hashCode(windowSizeMs);
+            result = 31 * result + countLimit;
+            result = 31 * result + countInWindow;
+            result = 31 * result + Long.hashCode(inQuotaTimeElapsed);
+            return result;
+        }
+    }
+
+    /** List of times of all instantaneous events for a UPTC, in chronological order. */
+    // TODO(146148168): introduce a bucketized mode that's more efficient but less accurate
+    @GuardedBy("mLock")
+    private final UptcMap<LongArrayQueue> mEventTimes = new UptcMap<>();
+
+    /** Cached calculation results for each app. */
+    @GuardedBy("mLock")
+    private final UptcMap<ExecutionStats> mExecutionStatsCache = new UptcMap<>();
+
+    private final Handler mHandler;
+
+    @GuardedBy("mLock")
+    private long mNextCleanupTimeElapsed = 0;
+    @GuardedBy("mLock")
+    private final AlarmManager.OnAlarmListener mEventCleanupAlarmListener = () ->
+            CountQuotaTracker.this.mHandler.obtainMessage(MSG_CLEAN_UP_EVENTS).sendToTarget();
+
+    /** The rolling window size for each Category's count limit. */
+    @GuardedBy("mLock")
+    private final ArrayMap<Category, Long> mCategoryCountWindowSizesMs = new ArrayMap<>();
+
+    /**
+     * The maximum count for each Category. For each max value count in the map, the app will
+     * not be allowed any more events within the latest time interval of its rolling window size.
+     *
+     * @see #mCategoryCountWindowSizesMs
+     */
+    @GuardedBy("mLock")
+    private final ArrayMap<Category, Integer> mMaxCategoryCounts = new ArrayMap<>();
+
+    /** The longest period a registered category applies to. */
+    @GuardedBy("mLock")
+    private long mMaxPeriodMs = 0;
+
+    /** Drop any old events. */
+    private static final int MSG_CLEAN_UP_EVENTS = 1;
+
+    public CountQuotaTracker(@NonNull Context context, @NonNull Categorizer categorizer) {
+        this(context, categorizer, new Injector());
+    }
+
+    @VisibleForTesting
+    CountQuotaTracker(@NonNull Context context, @NonNull Categorizer categorizer,
+            Injector injector) {
+        super(context, categorizer, injector);
+
+        mHandler = new CqtHandler(context.getMainLooper());
+    }
+
+    // Exposed API to users.
+
+    /**
+     * Record that an instantaneous event happened.
+     *
+     * @return true if the UPTC is within quota, false otherwise.
+     */
+    public boolean noteEvent(int userId, @NonNull String packageName, @Nullable String tag) {
+        synchronized (mLock) {
+            if (!isEnabledLocked() || isQuotaFreeLocked(userId, packageName)) {
+                return true;
+            }
+            final long nowElapsed = mInjector.getElapsedRealtime();
+
+            final LongArrayQueue times = mEventTimes
+                    .getOrCreate(userId, packageName, tag, mCreateLongArrayQueue);
+            times.addLast(nowElapsed);
+            final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, tag);
+            stats.countInWindow++;
+            stats.expirationTimeElapsed = Math.min(stats.expirationTimeElapsed,
+                    nowElapsed + stats.windowSizeMs);
+            if (stats.countInWindow == stats.countLimit) {
+                final long windowEdgeElapsed = nowElapsed - stats.windowSizeMs;
+                while (times.size() > 0 && times.peekFirst() < windowEdgeElapsed) {
+                    times.removeFirst();
+                }
+                stats.inQuotaTimeElapsed = times.peekFirst() + stats.windowSizeMs;
+                postQuotaStatusChanged(userId, packageName, tag);
+            } else if (stats.countLimit > 9
+                    && stats.countInWindow == stats.countLimit * 4 / 5) {
+                // TODO: log high watermark to statsd
+                Slog.w(TAG, string(userId, packageName, tag)
+                        + " has reached 80% of it's count limit of " + stats.countLimit);
+            }
+            maybeScheduleCleanupAlarmLocked();
+            return isWithinQuotaLocked(stats);
+        }
+    }
+
+    /**
+     * Set count limit over a rolling time window for the specified category.
+     *
+     * @param category     The category these limits apply to.
+     * @param limit        The maximum event count an app can have in the rolling window. Must be
+     *                     nonnegative.
+     * @param timeWindowMs The rolling time window (in milliseconds) to use when checking quota
+     *                     usage. Must be at least {@value #MIN_WINDOW_SIZE_MS} and no longer than
+     *                     {@value #MAX_WINDOW_SIZE_MS}
+     */
+    public void setCountLimit(@NonNull Category category, int limit, long timeWindowMs) {
+        if (limit < 0 || timeWindowMs < 0) {
+            throw new IllegalArgumentException("Limit and window size must be nonnegative.");
+        }
+        synchronized (mLock) {
+            final Integer oldLimit = mMaxCategoryCounts.put(category, limit);
+            final long newWindowSizeMs = Math.max(MIN_WINDOW_SIZE_MS,
+                    Math.min(timeWindowMs, MAX_WINDOW_SIZE_MS));
+            final Long oldWindowSizeMs = mCategoryCountWindowSizesMs.put(category, newWindowSizeMs);
+            if (oldLimit != null && oldWindowSizeMs != null
+                    && oldLimit == limit && oldWindowSizeMs == newWindowSizeMs) {
+                // No change.
+                return;
+            }
+            mDeleteOldEventTimesFunctor.updateMaxPeriod();
+            mMaxPeriodMs = mDeleteOldEventTimesFunctor.mMaxPeriodMs;
+            invalidateAllExecutionStatsLocked();
+        }
+        scheduleQuotaCheck();
+    }
+
+    /**
+     * Gets the count limit for the specified category.
+     */
+    public int getLimit(@NonNull Category category) {
+        synchronized (mLock) {
+            final Integer limit = mMaxCategoryCounts.get(category);
+            if (limit == null) {
+                throw new IllegalArgumentException("Limit for " + category + " not defined");
+            }
+            return limit;
+        }
+    }
+
+    /**
+     * Gets the count time window for the specified category.
+     */
+    public long getWindowSizeMs(@NonNull Category category) {
+        synchronized (mLock) {
+            final Long limitMs = mCategoryCountWindowSizesMs.get(category);
+            if (limitMs == null) {
+                throw new IllegalArgumentException("Limit for " + category + " not defined");
+            }
+            return limitMs;
+        }
+    }
+
+    // Internal implementation.
+
+    @Override
+    @GuardedBy("mLock")
+    void dropEverythingLocked() {
+        mExecutionStatsCache.clear();
+        mEventTimes.clear();
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    @NonNull
+    Handler getHandler() {
+        return mHandler;
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    long getInQuotaTimeElapsedLocked(final int userId, @NonNull final String packageName,
+            @Nullable final String tag) {
+        return getExecutionStatsLocked(userId, packageName, tag).inQuotaTimeElapsed;
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    void handleRemovedAppLocked(String packageName, int uid) {
+        if (packageName == null) {
+            Slog.wtf(TAG, "Told app removed but given null package name.");
+            return;
+        }
+        final int userId = UserHandle.getUserId(uid);
+
+        mEventTimes.delete(userId, packageName);
+        mExecutionStatsCache.delete(userId, packageName);
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    void handleRemovedUserLocked(int userId) {
+        mEventTimes.delete(userId);
+        mExecutionStatsCache.delete(userId);
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
+            @Nullable final String tag) {
+        if (!isEnabledLocked()) return true;
+
+        // Quota constraint is not enforced when quota is free.
+        if (isQuotaFreeLocked(userId, packageName)) {
+            return true;
+        }
+
+        return isWithinQuotaLocked(getExecutionStatsLocked(userId, packageName, tag));
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    void maybeUpdateAllQuotaStatusLocked() {
+        final UptcMap<Boolean> doneMap = new UptcMap<>();
+        mEventTimes.forEach((userId, packageName, tag, events) -> {
+            if (!doneMap.contains(userId, packageName, tag)) {
+                maybeUpdateStatusForUptcLocked(userId, packageName, tag);
+                doneMap.add(userId, packageName, tag, Boolean.TRUE);
+            }
+        });
+
+    }
+
+    @Override
+    void maybeUpdateQuotaStatus(final int userId, @NonNull final String packageName,
+            @Nullable final String tag) {
+        synchronized (mLock) {
+            maybeUpdateStatusForUptcLocked(userId, packageName, tag);
+        }
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    void onQuotaFreeChangedLocked(boolean isFree) {
+        // Nothing to do here.
+    }
+
+    @Override
+    @GuardedBy("mLock")
+    void onQuotaFreeChangedLocked(int userId, @NonNull String packageName, boolean isFree) {
+        maybeUpdateStatusForPkgLocked(userId, packageName);
+    }
+
+    @GuardedBy("mLock")
+    private boolean isWithinQuotaLocked(@NonNull final ExecutionStats stats) {
+        return isUnderCountQuotaLocked(stats);
+    }
+
+    @GuardedBy("mLock")
+    private boolean isUnderCountQuotaLocked(@NonNull ExecutionStats stats) {
+        return stats.countInWindow < stats.countLimit;
+    }
+
+    /** Returns the execution stats of the app in the most recent window. */
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    @NonNull
+    ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName,
+            @Nullable final String tag) {
+        return getExecutionStatsLocked(userId, packageName, tag, true);
+    }
+
+    @GuardedBy("mLock")
+    @NonNull
+    private ExecutionStats getExecutionStatsLocked(final int userId,
+            @NonNull final String packageName, @Nullable String tag,
+            final boolean refreshStatsIfOld) {
+        final ExecutionStats stats =
+                mExecutionStatsCache.getOrCreate(userId, packageName, tag, mCreateExecutionStats);
+        if (refreshStatsIfOld) {
+            final Category category = mCategorizer.getCategory(userId, packageName, tag);
+            final long countWindowSizeMs = mCategoryCountWindowSizesMs.getOrDefault(category,
+                    Long.MAX_VALUE);
+            final int countLimit = mMaxCategoryCounts.getOrDefault(category, Integer.MAX_VALUE);
+            if (stats.expirationTimeElapsed <= mInjector.getElapsedRealtime()
+                    || stats.windowSizeMs != countWindowSizeMs
+                    || stats.countLimit != countLimit) {
+                // The stats are no longer valid.
+                stats.windowSizeMs = countWindowSizeMs;
+                stats.countLimit = countLimit;
+                updateExecutionStatsLocked(userId, packageName, tag, stats);
+            }
+        }
+
+        return stats;
+    }
+
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    void updateExecutionStatsLocked(final int userId, @NonNull final String packageName,
+            @Nullable final String tag, @NonNull ExecutionStats stats) {
+        stats.countInWindow = 0;
+        stats.inQuotaTimeElapsed = 0;
+
+        // This can be used to determine when an app will have enough quota to transition from
+        // out-of-quota to in-quota.
+        final long nowElapsed = mInjector.getElapsedRealtime();
+        stats.expirationTimeElapsed = nowElapsed + mMaxPeriodMs;
+
+        final LongArrayQueue events = mEventTimes.get(userId, packageName, tag);
+        if (events == null) {
+            return;
+        }
+
+        // The minimum time between the start time and the beginning of the events that were
+        // looked at --> how much time the stats will be valid for.
+        long emptyTimeMs = Long.MAX_VALUE - nowElapsed;
+
+        final long eventStartWindowElapsed = nowElapsed - stats.windowSizeMs;
+        for (int i = events.size() - 1; i >= 0; --i) {
+            final long eventTimeElapsed = events.get(i);
+            if (eventTimeElapsed < eventStartWindowElapsed) {
+                // This event happened before the window. No point in going any further.
+                break;
+            }
+            stats.countInWindow++;
+            emptyTimeMs = Math.min(emptyTimeMs, eventTimeElapsed - eventStartWindowElapsed);
+
+            if (stats.countInWindow >= stats.countLimit) {
+                stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+                        eventTimeElapsed + stats.windowSizeMs);
+            }
+        }
+
+        stats.expirationTimeElapsed = nowElapsed + emptyTimeMs;
+    }
+
+    /** Invalidate ExecutionStats for all apps. */
+    @GuardedBy("mLock")
+    private void invalidateAllExecutionStatsLocked() {
+        final long nowElapsed = mInjector.getElapsedRealtime();
+        mExecutionStatsCache.forEach((appStats) -> {
+            if (appStats != null) {
+                appStats.expirationTimeElapsed = nowElapsed;
+            }
+        });
+    }
+
+    @GuardedBy("mLock")
+    private void invalidateAllExecutionStatsLocked(final int userId,
+            @NonNull final String packageName) {
+        final ArrayMap<String, ExecutionStats> appStats =
+                mExecutionStatsCache.get(userId, packageName);
+        if (appStats != null) {
+            final long nowElapsed = mInjector.getElapsedRealtime();
+            final int numStats = appStats.size();
+            for (int i = 0; i < numStats; ++i) {
+                final ExecutionStats stats = appStats.valueAt(i);
+                if (stats != null) {
+                    stats.expirationTimeElapsed = nowElapsed;
+                }
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void invalidateExecutionStatsLocked(final int userId, @NonNull final String packageName,
+            @Nullable String tag) {
+        final ExecutionStats stats = mExecutionStatsCache.get(userId, packageName, tag);
+        if (stats != null) {
+            stats.expirationTimeElapsed = mInjector.getElapsedRealtime();
+        }
+    }
+
+    private static final class EarliestEventTimeFunctor implements Consumer<LongArrayQueue> {
+        long earliestTimeElapsed = Long.MAX_VALUE;
+
+        @Override
+        public void accept(LongArrayQueue events) {
+            if (events != null && events.size() > 0) {
+                earliestTimeElapsed = Math.min(earliestTimeElapsed, events.get(0));
+            }
+        }
+
+        void reset() {
+            earliestTimeElapsed = Long.MAX_VALUE;
+        }
+    }
+
+    private final EarliestEventTimeFunctor mEarliestEventTimeFunctor =
+            new EarliestEventTimeFunctor();
+
+    /** Schedule a cleanup alarm if necessary and there isn't already one scheduled. */
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    void maybeScheduleCleanupAlarmLocked() {
+        if (mNextCleanupTimeElapsed > mInjector.getElapsedRealtime()) {
+            // There's already an alarm scheduled. Just stick with that one. There's no way we'll
+            // end up scheduling an earlier alarm.
+            if (DEBUG) {
+                Slog.v(TAG, "Not scheduling cleanup since there's already one at "
+                        + mNextCleanupTimeElapsed + " (in " + (mNextCleanupTimeElapsed
+                        - mInjector.getElapsedRealtime()) + "ms)");
+            }
+            return;
+        }
+
+        mEarliestEventTimeFunctor.reset();
+        mEventTimes.forEach(mEarliestEventTimeFunctor);
+        final long earliestEndElapsed = mEarliestEventTimeFunctor.earliestTimeElapsed;
+        if (earliestEndElapsed == Long.MAX_VALUE) {
+            // Couldn't find a good time to clean up. Maybe this was called after we deleted all
+            // events.
+            if (DEBUG) {
+                Slog.d(TAG, "Didn't find a time to schedule cleanup");
+            }
+            return;
+        }
+
+        // Need to keep events for all apps up to the max period, regardless of their current
+        // category.
+        long nextCleanupElapsed = earliestEndElapsed + mMaxPeriodMs;
+        if (nextCleanupElapsed - mNextCleanupTimeElapsed <= 10 * MINUTE_IN_MILLIS) {
+            // No need to clean up too often. Delay the alarm if the next cleanup would be too soon
+            // after it.
+            nextCleanupElapsed += 10 * MINUTE_IN_MILLIS;
+        }
+        mNextCleanupTimeElapsed = nextCleanupElapsed;
+        scheduleAlarm(AlarmManager.ELAPSED_REALTIME, nextCleanupElapsed, ALARM_TAG_CLEANUP,
+                mEventCleanupAlarmListener);
+        if (DEBUG) {
+            Slog.d(TAG, "Scheduled next cleanup for " + mNextCleanupTimeElapsed);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private boolean maybeUpdateStatusForPkgLocked(final int userId,
+            @NonNull final String packageName) {
+        final UptcMap<Boolean> done = new UptcMap<>();
+
+        if (!mEventTimes.contains(userId, packageName)) {
+            return false;
+        }
+        final ArrayMap<String, LongArrayQueue> events = mEventTimes.get(userId, packageName);
+        if (events == null) {
+            Slog.wtf(TAG,
+                    "Events map was null even though mEventTimes said it contained "
+                            + string(userId, packageName, null));
+            return false;
+        }
+
+        // Lambdas can't interact with non-final outer variables.
+        final boolean[] changed = {false};
+        events.forEach((tag, eventList) -> {
+            if (!done.contains(userId, packageName, tag)) {
+                changed[0] |= maybeUpdateStatusForUptcLocked(userId, packageName, tag);
+                done.add(userId, packageName, tag, Boolean.TRUE);
+            }
+        });
+
+        return changed[0];
+    }
+
+    /**
+     * Posts that the quota status for the UPTC has changed if it has changed. Avoid calling if
+     * there are no {@link QuotaChangeListener}s registered as the work done will be useless.
+     *
+     * @return true if the in/out quota status changed
+     */
+    @GuardedBy("mLock")
+    private boolean maybeUpdateStatusForUptcLocked(final int userId,
+            @NonNull final String packageName, @Nullable final String tag) {
+        final boolean oldInQuota = isWithinQuotaLocked(
+                getExecutionStatsLocked(userId, packageName, tag, false));
+
+        final boolean newInQuota;
+        if (!isEnabledLocked() || isQuotaFreeLocked(userId, packageName)) {
+            newInQuota = true;
+        } else {
+            newInQuota = isWithinQuotaLocked(
+                    getExecutionStatsLocked(userId, packageName, tag, true));
+        }
+
+        if (!newInQuota) {
+            maybeScheduleStartAlarmLocked(userId, packageName, tag);
+        } else {
+            cancelScheduledStartAlarmLocked(userId, packageName, tag);
+        }
+
+        if (oldInQuota != newInQuota) {
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "Quota status changed from " + oldInQuota + " to " + newInQuota + " for "
+                                + string(userId, packageName, tag));
+            }
+            postQuotaStatusChanged(userId, packageName, tag);
+            return true;
+        }
+
+        return false;
+    }
+
+    private final class DeleteEventTimesFunctor implements Consumer<LongArrayQueue> {
+        private long mMaxPeriodMs;
+
+        @Override
+        public void accept(LongArrayQueue times) {
+            if (times != null) {
+                // Remove everything older than mMaxPeriodMs time ago.
+                while (times.size() > 0
+                        && times.peekFirst() <= mInjector.getElapsedRealtime() - mMaxPeriodMs) {
+                    times.removeFirst();
+                }
+            }
+        }
+
+        private void updateMaxPeriod() {
+            long maxPeriodMs = 0;
+            for (int i = mCategoryCountWindowSizesMs.size() - 1; i >= 0; --i) {
+                maxPeriodMs = Long.max(maxPeriodMs, mCategoryCountWindowSizesMs.valueAt(i));
+            }
+            mMaxPeriodMs = maxPeriodMs;
+        }
+    }
+
+    private final DeleteEventTimesFunctor mDeleteOldEventTimesFunctor =
+            new DeleteEventTimesFunctor();
+
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    void deleteObsoleteEventsLocked() {
+        mEventTimes.forEach(mDeleteOldEventTimesFunctor);
+    }
+
+    private class CqtHandler extends Handler {
+        CqtHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            synchronized (mLock) {
+                switch (msg.what) {
+                    case MSG_CLEAN_UP_EVENTS: {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Cleaning up events.");
+                        }
+                        deleteObsoleteEventsLocked();
+                        maybeScheduleCleanupAlarmLocked();
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    private Function<Void, LongArrayQueue> mCreateLongArrayQueue = aVoid -> new LongArrayQueue();
+    private Function<Void, ExecutionStats> mCreateExecutionStats = aVoid -> new ExecutionStats();
+
+    //////////////////////// TESTING HELPERS /////////////////////////////
+
+    @VisibleForTesting
+    @Nullable
+    LongArrayQueue getEvents(int userId, String packageName, String tag) {
+        return mEventTimes.get(userId, packageName, tag);
+    }
+
+    //////////////////////////// DATA DUMP //////////////////////////////
+
+    /** Dump state in text format. */
+    public void dump(final IndentingPrintWriter pw) {
+        pw.print(TAG);
+        pw.println(":");
+        pw.increaseIndent();
+
+        synchronized (mLock) {
+            super.dump(pw);
+            pw.println();
+
+            pw.println("Instantaneous events:");
+            pw.increaseIndent();
+            mEventTimes.forEach((userId, pkgName, tag, events) -> {
+                if (events.size() > 0) {
+                    pw.print(string(userId, pkgName, tag));
+                    pw.println(":");
+                    pw.increaseIndent();
+                    pw.print(events.get(0));
+                    for (int i = 1; i < events.size(); ++i) {
+                        pw.print(", ");
+                        pw.print(events.get(i));
+                    }
+                    pw.decreaseIndent();
+                    pw.println();
+                }
+            });
+            pw.decreaseIndent();
+
+            pw.println();
+            pw.println("Cached execution stats:");
+            pw.increaseIndent();
+            mExecutionStatsCache.forEach((userId, pkgName, tag, stats) -> {
+                if (stats != null) {
+                    pw.print(string(userId, pkgName, tag));
+                    pw.println(":");
+                    pw.increaseIndent();
+                    pw.println(stats);
+                    pw.decreaseIndent();
+                }
+            });
+            pw.decreaseIndent();
+
+            pw.println();
+            pw.println("Limits:");
+            pw.increaseIndent();
+            final int numCategories = mCategoryCountWindowSizesMs.size();
+            for (int i = 0; i < numCategories; ++i) {
+                final Category category = mCategoryCountWindowSizesMs.keyAt(i);
+                pw.print(category);
+                pw.print(": ");
+                pw.print(mMaxCategoryCounts.get(category));
+                pw.print(" events in ");
+                pw.println(TimeUtils.formatDuration(mCategoryCountWindowSizesMs.get(category)));
+            }
+            pw.decreaseIndent();
+        }
+        pw.decreaseIndent();
+    }
+
+    /**
+     * Dump state to proto.
+     *
+     * @param proto   The ProtoOutputStream to write to.
+     * @param fieldId The field ID of the {@link CountQuotaTrackerProto}.
+     */
+    public void dump(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+
+        synchronized (mLock) {
+            super.dump(proto, CountQuotaTrackerProto.BASE_QUOTA_DATA);
+
+            for (int i = 0; i < mCategoryCountWindowSizesMs.size(); ++i) {
+                final Category category = mCategoryCountWindowSizesMs.keyAt(i);
+                final long clToken = proto.start(CountQuotaTrackerProto.COUNT_LIMIT);
+                category.dumpDebug(proto, CountQuotaTrackerProto.CountLimit.CATEGORY);
+                proto.write(CountQuotaTrackerProto.CountLimit.LIMIT,
+                        mMaxCategoryCounts.get(category));
+                proto.write(CountQuotaTrackerProto.CountLimit.WINDOW_SIZE_MS,
+                        mCategoryCountWindowSizesMs.get(category));
+                proto.end(clToken);
+            }
+
+            mExecutionStatsCache.forEach((userId, pkgName, tag, stats) -> {
+                final boolean isQuotaFree = isIndividualQuotaFreeLocked(userId, pkgName);
+
+                final long usToken = proto.start(CountQuotaTrackerProto.UPTC_STATS);
+
+                (new Uptc(userId, pkgName, tag))
+                        .dumpDebug(proto, CountQuotaTrackerProto.UptcStats.UPTC);
+
+                proto.write(CountQuotaTrackerProto.UptcStats.IS_QUOTA_FREE, isQuotaFree);
+
+                final LongArrayQueue events = mEventTimes.get(userId, pkgName, tag);
+                if (events != null) {
+                    for (int j = events.size() - 1; j >= 0; --j) {
+                        final long eToken = proto.start(CountQuotaTrackerProto.UptcStats.EVENTS);
+                        proto.write(CountQuotaTrackerProto.Event.TIMESTAMP_ELAPSED, events.get(j));
+                        proto.end(eToken);
+                    }
+                }
+
+                final long statsToken = proto.start(
+                        CountQuotaTrackerProto.UptcStats.EXECUTION_STATS);
+                proto.write(
+                        CountQuotaTrackerProto.ExecutionStats.EXPIRATION_TIME_ELAPSED,
+                        stats.expirationTimeElapsed);
+                proto.write(
+                        CountQuotaTrackerProto.ExecutionStats.WINDOW_SIZE_MS,
+                        stats.windowSizeMs);
+                proto.write(CountQuotaTrackerProto.ExecutionStats.COUNT_LIMIT, stats.countLimit);
+                proto.write(
+                        CountQuotaTrackerProto.ExecutionStats.COUNT_IN_WINDOW,
+                        stats.countInWindow);
+                proto.write(
+                        CountQuotaTrackerProto.ExecutionStats.IN_QUOTA_TIME_ELAPSED,
+                        stats.inQuotaTimeElapsed);
+                proto.end(statsToken);
+
+                proto.end(usToken);
+            });
+
+            proto.end(token);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/utils/quota/UptcMap.java b/services/core/java/com/android/server/utils/quota/UptcMap.java
index 80f4f0a..a3d6ee5 100644
--- a/services/core/java/com/android/server/utils/quota/UptcMap.java
+++ b/services/core/java/com/android/server/utils/quota/UptcMap.java
@@ -112,37 +112,20 @@
         return data.get(tag);
     }
 
-    /**
-     * Returns the index for which {@link #getUserIdAtIndex(int)} would return the specified userId,
-     * or a negative number if the specified userId is not mapped.
-     */
-    public int indexOfUserId(int userId) {
-        return mData.indexOfKey(userId);
-    }
-
-    /**
-     * Returns the index for which {@link #getPackageNameAtIndex(int, int)} would return the
-     * specified userId, or a negative number if the specified userId and packageName are not mapped
-     * together.
-     */
-    public int indexOfUserIdAndPackage(int userId, @NonNull String packageName) {
-        return mData.indexOfKey(userId, packageName);
-    }
-
     /** Returns the userId at the given index. */
-    public int getUserIdAtIndex(int index) {
+    private int getUserIdAtIndex(int index) {
         return mData.keyAt(index);
     }
 
     /** Returns the package name at the given index. */
     @NonNull
-    public String getPackageNameAtIndex(int userIndex, int packageIndex) {
+    private String getPackageNameAtIndex(int userIndex, int packageIndex) {
         return mData.keyAt(userIndex, packageIndex);
     }
 
     /** Returns the tag at the given index. */
     @NonNull
-    public String getTagAtIndex(int userIndex, int packageIndex, int tagIndex) {
+    private String getTagAtIndex(int userIndex, int packageIndex, int tagIndex) {
         // This structure never inserts a null ArrayMap, so if the indices are valid, valueAt()
         // won't return null.
         return mData.valueAt(userIndex, packageIndex).keyAt(tagIndex);
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 94c2192..23b94bd 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -751,10 +751,10 @@
         if (abort) {
             launchObserverNotifyActivityLaunchCancelled(info);
         } else {
-            logAppTransitionFinished(info);
             if (info.isInterestingToLoggerAndObserver()) {
                 launchObserverNotifyActivityLaunchFinished(info, timestampNs);
             }
+            logAppTransitionFinished(info);
         }
         info.mPendingDrawActivities.clear();
         mTransitionInfoList.remove(info);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 65d0c4c..e281712 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6107,11 +6107,6 @@
                 // This also avoids if the next activity never reports idle (e.g. animating view),
                 // the previous will need to wait until idle timeout to be stopped or destroyed.
                 mStackSupervisor.scheduleProcessStoppingAndFinishingActivities();
-            } else {
-                // Instead of doing the full stop routine here, let's just hide any activities
-                // we now can, and let them stop when the normal idle happens.
-                mStackSupervisor.processStoppingActivities(null /* launchedActivity */,
-                        true /* onlyUpdateVisibility */, true /* unused */, null /* unused */);
             }
         }
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index fe03c06..c959439 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -784,6 +784,10 @@
                         null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
                         null /* tempOtherTaskBounds */, null /* tempOtherTaskInsetBounds */,
                         PRESERVE_WINDOWS, true /* deferResume */);
+            } else if (overrideWindowingMode != WINDOWING_MODE_PINNED) {
+                // For pinned stack, resize is now part of the {@link WindowContainerTransaction}
+                resize(new Rect(newBounds), null /* tempTaskBounds */,
+                        null /* tempTaskInsetBounds */, PRESERVE_WINDOWS, true /* deferResume */);
             }
         }
         if (prevIsAlwaysOnTop != isAlwaysOnTop()) {
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index e8564fc..fd871bd 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -2076,54 +2076,11 @@
             boolean processPausingActivities, String reason) {
         // Stop any activities that are scheduled to do so but have been waiting for the transition
         // animation to finish.
-        processStoppingActivities(launchedActivity, false /* onlyUpdateVisibility */,
-                processPausingActivities, reason);
-
-        final int numFinishingActivities = mFinishingActivities.size();
-        if (numFinishingActivities == 0) {
-            return;
-        }
-
-        // Finish any activities that are scheduled to do so but have been waiting for the next one
-        // to start.
-        final ArrayList<ActivityRecord> finishingActivities = new ArrayList<>(mFinishingActivities);
-        mFinishingActivities.clear();
-        for (int i = 0; i < numFinishingActivities; i++) {
-            final ActivityRecord r = finishingActivities.get(i);
-            if (r.isInHistory()) {
-                r.destroyImmediately(true /* removeFromApp */, "finish-" + reason);
-            }
-        }
-    }
-
-    /** Stop or destroy the stopping activities if they are not interactive. */
-    void processStoppingActivities(ActivityRecord launchedActivity, boolean onlyUpdateVisibility,
-            boolean processPausingActivities, String reason) {
-        final int numStoppingActivities = mStoppingActivities.size();
-        if (numStoppingActivities == 0) {
-            return;
-        }
         ArrayList<ActivityRecord> readyToStopActivities = null;
-
-        final boolean nowVisible = mRootWindowContainer.allResumedActivitiesVisible();
-        for (int activityNdx = numStoppingActivities - 1; activityNdx >= 0; --activityNdx) {
-            final ActivityRecord s = mStoppingActivities.get(activityNdx);
-            if (nowVisible && s.finishing) {
-
-                // If this activity is finishing, it is sitting on top of
-                // everyone else but we now know it is no longer needed...
-                // so get rid of it.  Otherwise, we need to go through the
-                // normal flow and hide it once we determine that it is
-                // hidden by the activities in front of it.
-                if (DEBUG_STATES) Slog.v(TAG, "Before stopping, can hide: " + s);
-                s.setVisibility(false);
-            }
-            if (onlyUpdateVisibility) {
-                continue;
-            }
-
+        for (int i = mStoppingActivities.size() - 1; i >= 0; --i) {
+            final ActivityRecord s = mStoppingActivities.get(i);
             final boolean animating = s.isAnimating(TRANSITION);
-            if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + nowVisible
+            if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + s.nowVisible
                     + " animating=" + animating + " finishing=" + s.finishing);
 
             final ActivityStack stack = s.getActivityStack();
@@ -2145,7 +2102,7 @@
                 }
                 readyToStopActivities.add(s);
 
-                mStoppingActivities.remove(activityNdx);
+                mStoppingActivities.remove(i);
             }
         }
 
@@ -2161,6 +2118,22 @@
                 }
             }
         }
+
+        final int numFinishingActivities = mFinishingActivities.size();
+        if (numFinishingActivities == 0) {
+            return;
+        }
+
+        // Finish any activities that are scheduled to do so but have been waiting for the next one
+        // to start.
+        final ArrayList<ActivityRecord> finishingActivities = new ArrayList<>(mFinishingActivities);
+        mFinishingActivities.clear();
+        for (int i = 0; i < numFinishingActivities; i++) {
+            final ActivityRecord r = finishingActivities.get(i);
+            if (r.isInHistory()) {
+                r.destroyImmediately(true /* removeFromApp */, "finish-" + reason);
+            }
+        }
     }
 
     void removeHistoryRecords(WindowProcessController app) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0b6a6e0..472baf6 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -772,7 +772,11 @@
             // If this is the first layout, we need to initialize the last frames and inset values,
             // as otherwise we'd immediately cause an unnecessary resize.
             if (firstLayout) {
-                w.updateLastFrames();
+                // The client may compute its actual requested size according to the first layout,
+                // so we still request the window to resize if the current frame is empty.
+                if (!w.getFrameLw().isEmpty()) {
+                    w.updateLastFrames();
+                }
                 w.updateLastInsetValues();
                 w.updateLocationInParentDisplayIfNeeded();
             }
@@ -1032,31 +1036,13 @@
         // Sets the display content for the children.
         onDisplayChanged(this);
 
-        // Add itself as a child to the root container.
-        mWmService.mRoot.addChild(this, POSITION_BOTTOM);
-
-        // TODO(b/62541591): evaluate whether this is the best spot to declare the
-        // {@link DisplayContent} ready for use.
-        mDisplayReady = true;
-
-        mWmService.mAnimator.addDisplayLocked(mDisplayId);
-        mInputMonitor = new InputMonitor(mWmService, mDisplayId);
+        mInputMonitor = new InputMonitor(mWmService, this);
         mInsetsStateController = new InsetsStateController(this);
         mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
 
-        if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display);
+        if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display);
 
         mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
-
-        if (mWmService.mDisplayManagerInternal != null) {
-            mWmService.mDisplayManagerInternal
-                    .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo());
-            configureDisplayPolicy();
-        }
-
-        reconfigureDisplayLocked();
-        onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
-        mWmService.mDisplayNotificationController.dispatchDisplayAdded(this);
     }
 
     boolean isReady() {
@@ -4992,6 +4978,24 @@
         // we create the root surfaces explicitly rather than chaining
         // up as the default implementation in onParentChanged does. So we
         // explicitly do NOT call super here.
+
+        if (!isReady()) {
+            // TODO(b/62541591): evaluate whether this is the best spot to declare the
+            // {@link DisplayContent} ready for use.
+            mDisplayReady = true;
+
+            mWmService.mAnimator.addDisplayLocked(mDisplayId);
+
+            if (mWmService.mDisplayManagerInternal != null) {
+                mWmService.mDisplayManagerInternal
+                        .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo());
+                configureDisplayPolicy();
+            }
+
+            reconfigureDisplayLocked();
+            onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
+            mWmService.mDisplayNotificationController.dispatchDisplayAdded(this);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 1f9f883..5b892f8 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -10,23 +10,40 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS;
 
+import android.os.Build;
 import android.os.Debug;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArrayMap;
 import android.util.Slog;
 import android.view.IWindow;
 import android.view.InputApplicationHandle;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 
+import com.android.server.am.ActivityManagerService;
 import com.android.server.input.InputManagerService;
 import com.android.server.wm.EmbeddedWindowController.EmbeddedWindow;
 
+import java.io.File;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
 final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "InputManagerCallback" : TAG_WM;
+
+    /** Prevent spamming the traces because pre-dump cannot aware duplicated ANR. */
+    private static final long PRE_DUMP_MIN_INTERVAL_MS = TimeUnit.SECONDS.toMillis(20);
+    /** The timeout to detect if a monitor is held for a while. */
+    private static final long PRE_DUMP_MONITOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1);
+    /** The last time pre-dump was executed. */
+    private volatile long mLastPreDumpTimeMs;
+
     private final WindowManagerService mService;
 
     // Set to true when the first input device configuration change notification
@@ -77,6 +94,77 @@
     }
 
     /**
+     * Pre-dump stack trace if the locks of activity manager or window manager (they may be locked
+     * in the path of reporting ANR) cannot be acquired in time. That provides the stack traces
+     * before the real blocking symptom has gone.
+     * <p>
+     * Do not hold the {@link WindowManagerGlobalLock} while calling this method.
+     */
+    private void preDumpIfLockTooSlow() {
+        if (!Build.IS_DEBUGGABLE)  {
+            return;
+        }
+        final long now = SystemClock.uptimeMillis();
+        if (mLastPreDumpTimeMs > 0 && now - mLastPreDumpTimeMs < PRE_DUMP_MIN_INTERVAL_MS) {
+            return;
+        }
+
+        final boolean[] shouldDumpSf = { true };
+        final ArrayMap<String, Runnable> monitors = new ArrayMap<>(2);
+        monitors.put(TAG_WM, mService::monitor);
+        monitors.put("ActivityManager", mService.mAmInternal::monitor);
+        final CountDownLatch latch = new CountDownLatch(monitors.size());
+        // The pre-dump will execute if one of the monitors doesn't complete within the timeout.
+        for (int i = 0; i < monitors.size(); i++) {
+            final String name = monitors.keyAt(i);
+            final Runnable monitor = monitors.valueAt(i);
+            // Always create new thread to avoid noise of existing threads. Suppose here won't
+            // create too many threads because it means that watchdog will be triggered first.
+            new Thread() {
+                @Override
+                public void run() {
+                    monitor.run();
+                    latch.countDown();
+                    final long elapsed = SystemClock.uptimeMillis() - now;
+                    if (elapsed > PRE_DUMP_MONITOR_TIMEOUT_MS) {
+                        Slog.i(TAG_WM, "Pre-dump acquired " + name + " in " + elapsed + "ms");
+                    } else if (TAG_WM.equals(name)) {
+                        // Window manager is the main client of SurfaceFlinger. If window manager
+                        // is responsive, the stack traces of SurfaceFlinger may not be important.
+                        shouldDumpSf[0] = false;
+                    }
+                };
+            }.start();
+        }
+        try {
+            if (latch.await(PRE_DUMP_MONITOR_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                return;
+            }
+        } catch (InterruptedException ignored) { }
+        mLastPreDumpTimeMs = now;
+        Slog.i(TAG_WM, "Pre-dump for unresponsive");
+
+        final ArrayList<Integer> firstPids = new ArrayList<>(1);
+        firstPids.add(ActivityManagerService.MY_PID);
+        ArrayList<Integer> nativePids = null;
+        final int[] pids = shouldDumpSf[0]
+                ? Process.getPidsForCommands(new String[] { "/system/bin/surfaceflinger" })
+                : null;
+        if (pids != null) {
+            nativePids = new ArrayList<>(1);
+            for (int pid : pids) {
+                nativePids.add(pid);
+            }
+        }
+
+        final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
+                null /* processCpuTracker */, null /* lastPids */, nativePids);
+        if (tracesFile != null) {
+            tracesFile.renameTo(new File(tracesFile.getParent(), tracesFile.getName() + "_pre"));
+        }
+    }
+
+    /**
      * Notifies the window manager about an application that is not responding.
      * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
      *
@@ -89,6 +177,9 @@
         WindowState windowState = null;
         boolean aboveSystem = false;
         int windowPid = INVALID_PID;
+
+        preDumpIfLockTooSlow();
+
         //TODO(b/141764879) Limit scope of wm lock when input calls notifyANR
         synchronized (mService.mGlobalLock) {
 
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index a8ff500..c4b67d7 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -151,10 +151,10 @@
 
     private final UpdateInputWindows mUpdateInputWindows = new UpdateInputWindows();
 
-    public InputMonitor(WindowManagerService service, int displayId) {
+    InputMonitor(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
-        mDisplayContent = mService.mRoot.getDisplayContent(displayId);
-        mDisplayId = displayId;
+        mDisplayContent = displayContent;
+        mDisplayId = displayContent.getDisplayId();
         mInputTransaction = mService.mTransactionFactory.get();
         mHandler = mService.mAnimationHandler;
         mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 704ab67..a7bf660 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1377,6 +1377,7 @@
         for (int displayNdx = 0; displayNdx < displays.length; ++displayNdx) {
             final Display display = displays[displayNdx];
             final DisplayContent displayContent = new DisplayContent(display, this);
+            addChild(displayContent, POSITION_BOTTOM);
             if (displayContent.mDisplayId == DEFAULT_DISPLAY) {
                 mDefaultDisplay = displayContent;
             }
@@ -1445,6 +1446,7 @@
         }
         // The display hasn't been added to ActivityManager yet, create a new record now.
         displayContent = new DisplayContent(display, this);
+        addChild(displayContent, POSITION_BOTTOM);
         return displayContent;
     }
 
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 399c5d3..eaa0ea7 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -16,8 +16,12 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.AnimationSpecProto.ROTATE;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
+import static com.android.server.wm.RotationAnimationSpecProto.DURATION_MS;
+import static com.android.server.wm.RotationAnimationSpecProto.END_LUMA;
+import static com.android.server.wm.RotationAnimationSpecProto.START_LUMA;
 import static com.android.server.wm.ScreenRotationAnimationProto.ANIMATION_RUNNING;
 import static com.android.server.wm.ScreenRotationAnimationProto.STARTED;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -25,7 +29,9 @@
 import static com.android.server.wm.WindowManagerService.TYPE_LAYER_MULTIPLIER;
 import static com.android.server.wm.WindowStateAnimator.WINDOW_FREEZE_LAYER;
 
+import android.animation.ArgbEvaluator;
 import android.content.Context;
+import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -40,7 +46,9 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Transformation;
 
+import com.android.internal.R;
 import com.android.server.protolog.common.ProtoLog;
+import com.android.server.wm.utils.RotationAnimationUtils;
 
 import java.io.PrintWriter;
 
@@ -60,10 +68,10 @@
  *      animation first rotate the new content into the old orientation to then be able to
  *      animate to the new orientation
  *
- * <li> The exiting Blackframe: <p>
- *     Because the change of orientation might change the width and height of the content (i.e
- *     when rotating from portrait to landscape) we "crop" the new content using black frames
- *     around the screenshot so the new content does not go beyond the screenshot's bounds
+ * <li> The Background color frame: <p>
+ *      To have the animation seem more seamless, we add a color transitioning background behind the
+ *      exiting and entering layouts. We compute the brightness of the start and end
+ *      layouts and transition from the two brightness values as grayscale underneath the animation
  *
  * <li> The entering Blackframe: <p>
  *     The enter Blackframe is similar to the exit Blackframe but is only used when a custom
@@ -81,8 +89,6 @@
      */
     private static final int SCREEN_FREEZE_LAYER_BASE = WINDOW_FREEZE_LAYER + TYPE_LAYER_MULTIPLIER;
     private static final int SCREEN_FREEZE_LAYER_ENTER = SCREEN_FREEZE_LAYER_BASE;
-    private static final int SCREEN_FREEZE_LAYER_SCREENSHOT = SCREEN_FREEZE_LAYER_BASE + 1;
-    private static final int SCREEN_FREEZE_LAYER_EXIT = SCREEN_FREEZE_LAYER_BASE + 2;
 
     private final Context mContext;
     private final DisplayContent mDisplayContent;
@@ -90,16 +96,18 @@
     private final Transformation mRotateExitTransformation = new Transformation();
     private final Transformation mRotateEnterTransformation = new Transformation();
     // Complete transformations being applied.
-    private final Transformation mExitTransformation = new Transformation();
     private final Transformation mEnterTransformation = new Transformation();
-    private final Matrix mFrameInitialMatrix = new Matrix();
     private final Matrix mSnapshotInitialMatrix = new Matrix();
-    private final Matrix mSnapshotFinalMatrix = new Matrix();
-    private final Matrix mExitFrameFinalMatrix = new Matrix();
     private final WindowManagerService mService;
+    /** Only used for custom animations and not screen rotation. */
     private SurfaceControl mEnterBlackFrameLayer;
-    private SurfaceControl mRotationLayer;
-    private SurfaceControl mSurfaceControl;
+    /** This layer contains the actual screenshot that is to be faded out. */
+    private SurfaceControl mScreenshotLayer;
+    /**
+     * Only used for screen rotation and not custom animations. Layered behind all other layers
+     * to avoid showing any "empty" spots
+     */
+    private SurfaceControl mBackColorSurface;
     private BlackFrame mEnteringBlackFrame;
     private int mWidth, mHeight;
 
@@ -120,8 +128,11 @@
     private boolean mFinishAnimReady;
     private long mFinishAnimStartTime;
     private boolean mForceDefaultOrientation;
-    private BlackFrame mExitingBlackFrame;
     private SurfaceRotationAnimationController mSurfaceRotationAnimationController;
+    /** Intensity of light/whiteness of the layout before rotation occurs. */
+    private float mStartLuma;
+    /** Intensity of light/whiteness of the layout after rotation occurs. */
+    private float mEndLuma;
 
     public ScreenRotationAnimation(Context context, DisplayContent displayContent,
             boolean fixedToUserRotation, boolean isSecure, WindowManagerService service) {
@@ -162,9 +173,15 @@
 
         final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
         try {
-            mRotationLayer = displayContent.makeOverlay()
+            mBackColorSurface = displayContent.makeChildSurface(null)
+                    .setName("BackColorSurface")
+                    .setColorLayer()
+                    .build();
+
+            mScreenshotLayer = displayContent.makeOverlay()
                     .setName("RotationLayer")
-                    .setContainerLayer()
+                    .setBufferSize(mWidth, mHeight)
+                    .setSecure(isSecure)
                     .build();
 
             mEnterBlackFrameLayer = displayContent.makeOverlay()
@@ -172,26 +189,21 @@
                     .setContainerLayer()
                     .build();
 
-            mSurfaceControl = mService.makeSurfaceBuilder(null)
-                    .setName("ScreenshotSurface")
-                    .setParent(mRotationLayer)
-                    .setBufferSize(mWidth, mHeight)
-                    .setSecure(isSecure)
-                    .build();
-
             // In case display bounds change, screenshot buffer and surface may mismatch so set a
             // scaling mode.
             SurfaceControl.Transaction t2 = mService.mTransactionFactory.get();
-            t2.setOverrideScalingMode(mSurfaceControl, Surface.SCALING_MODE_SCALE_TO_WINDOW);
+            t2.setOverrideScalingMode(mScreenshotLayer, Surface.SCALING_MODE_SCALE_TO_WINDOW);
             t2.apply(true /* sync */);
 
             // Capture a screenshot into the surface we just created.
             final int displayId = display.getDisplayId();
             final Surface surface = mService.mSurfaceFactory.get();
-            surface.copyFrom(mSurfaceControl);
+            surface.copyFrom(mScreenshotLayer);
             SurfaceControl.ScreenshotGraphicBuffer gb =
                     mService.mDisplayManagerInternal.screenshot(displayId);
             if (gb != null) {
+                mStartLuma = RotationAnimationUtils.getAvgBorderLuma(gb.getGraphicBuffer(),
+                        gb.getColorSpace());
                 try {
                     surface.attachAndQueueBufferWithColorSpace(gb.getGraphicBuffer(),
                             gb.getColorSpace());
@@ -202,13 +214,15 @@
                 // screenshot surface we display it in also has FLAG_SECURE so that
                 // the user can not screenshot secure layers via the screenshot surface.
                 if (gb.containsSecureLayers()) {
-                    t.setSecure(mSurfaceControl, true);
+                    t.setSecure(mScreenshotLayer, true);
                 }
-                t.setLayer(mRotationLayer, SCREEN_FREEZE_LAYER_BASE);
-                t.setLayer(mSurfaceControl, SCREEN_FREEZE_LAYER_SCREENSHOT);
-                t.setAlpha(mSurfaceControl, 0);
-                t.show(mRotationLayer);
-                t.show(mSurfaceControl);
+                t.setLayer(mScreenshotLayer, SCREEN_FREEZE_LAYER_BASE);
+                t.reparent(mBackColorSurface, displayContent.getSurfaceControl());
+                t.setLayer(mBackColorSurface, -1);
+                t.setColor(mBackColorSurface, new float[]{mStartLuma, mStartLuma, mStartLuma});
+                t.setAlpha(mBackColorSurface, 1);
+                t.show(mScreenshotLayer);
+                t.show(mBackColorSurface);
             } else {
                 Slog.w(TAG, "Unable to take screenshot of display " + displayId);
             }
@@ -218,32 +232,11 @@
         }
 
         ProtoLog.i(WM_SHOW_SURFACE_ALLOC,
-                    "  FREEZE %s: CREATE", mSurfaceControl);
+                    "  FREEZE %s: CREATE", mScreenshotLayer);
         setRotation(t, originalRotation);
         t.apply();
     }
 
-    private static void createRotationMatrix(int rotation, int width, int height,
-            Matrix outMatrix) {
-        switch (rotation) {
-            case Surface.ROTATION_0:
-                outMatrix.reset();
-                break;
-            case Surface.ROTATION_90:
-                outMatrix.setRotate(90, 0, 0);
-                outMatrix.postTranslate(height, 0);
-                break;
-            case Surface.ROTATION_180:
-                outMatrix.setRotate(180, 0, 0);
-                outMatrix.postTranslate(width, height);
-                break;
-            case Surface.ROTATION_270:
-                outMatrix.setRotate(270, 0, 0);
-                outMatrix.postTranslate(0, width);
-                break;
-        }
-    }
-
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         proto.write(STARTED, mStarted);
@@ -252,11 +245,11 @@
     }
 
     boolean hasScreenshot() {
-        return mSurfaceControl != null;
+        return mScreenshotLayer != null;
     }
 
     private void setRotationTransform(SurfaceControl.Transaction t, Matrix matrix) {
-        if (mRotationLayer == null) {
+        if (mScreenshotLayer == null) {
             return;
         }
         matrix.getValues(mTmpFloats);
@@ -267,24 +260,19 @@
             x -= mCurrentDisplayRect.left;
             y -= mCurrentDisplayRect.top;
         }
-        t.setPosition(mRotationLayer, x, y);
-        t.setMatrix(mRotationLayer,
+        t.setPosition(mScreenshotLayer, x, y);
+        t.setMatrix(mScreenshotLayer,
                 mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
                 mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
 
-        t.setAlpha(mSurfaceControl, (float) 1.0);
-        t.setAlpha(mRotationLayer, (float) 1.0);
-        t.show(mRotationLayer);
+        t.setAlpha(mScreenshotLayer, (float) 1.0);
+        t.show(mScreenshotLayer);
     }
 
     public void printTo(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("mSurface="); pw.print(mSurfaceControl);
+        pw.print(prefix); pw.print("mSurface="); pw.print(mScreenshotLayer);
         pw.print(" mWidth="); pw.print(mWidth);
         pw.print(" mHeight="); pw.println(mHeight);
-        pw.print(prefix); pw.print("mExitingBlackFrame="); pw.println(mExitingBlackFrame);
-        if (mExitingBlackFrame != null) {
-            mExitingBlackFrame.printTo(prefix + "  ", pw);
-        }
         pw.print(prefix);
         pw.print("mEnteringBlackFrame=");
         pw.println(mEnteringBlackFrame);
@@ -303,20 +291,10 @@
         pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println();
         pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation);
         pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println();
-        pw.print(prefix); pw.print("mExitTransformation=");
-        mExitTransformation.printShortString(pw); pw.println();
         pw.print(prefix); pw.print("mEnterTransformation=");
         mEnterTransformation.printShortString(pw); pw.println();
-        pw.print(prefix); pw.print("mFrameInitialMatrix=");
-        mFrameInitialMatrix.printShortString(pw);
-        pw.println();
         pw.print(prefix); pw.print("mSnapshotInitialMatrix=");
-        mSnapshotInitialMatrix.printShortString(pw);
-        pw.print(" mSnapshotFinalMatrix="); mSnapshotFinalMatrix.printShortString(pw);
-        pw.println();
-        pw.print(prefix); pw.print("mExitFrameFinalMatrix=");
-        mExitFrameFinalMatrix.printShortString(pw);
-        pw.println();
+        mSnapshotInitialMatrix.printShortString(pw);pw.println();
         pw.print(prefix); pw.print("mForceDefaultOrientation="); pw.print(mForceDefaultOrientation);
         if (mForceDefaultOrientation) {
             pw.print(" mOriginalDisplayRect="); pw.print(mOriginalDisplayRect.toShortString());
@@ -331,7 +309,7 @@
         // to the snapshot to make it stay in the same original position
         // with the current screen rotation.
         int delta = DisplayContent.deltaRotation(rotation, Surface.ROTATION_0);
-        createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
+        RotationAnimationUtils.createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
 
         setRotationTransform(t, mSnapshotInitialMatrix);
     }
@@ -341,7 +319,7 @@
      */
     private boolean startAnimation(SurfaceControl.Transaction t, long maxAnimationDuration,
             float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
-        if (mSurfaceControl == null) {
+        if (mScreenshotLayer == null) {
             // Can't do animation.
             return false;
         }
@@ -354,89 +332,58 @@
         // Figure out how the screen has moved from the original rotation.
         int delta = DisplayContent.deltaRotation(mCurRotation, mOriginalRotation);
 
-        mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
-                com.android.internal.R.anim.screen_rotate_alpha);
 
         final boolean customAnim;
         if (exitAnim != 0 && enterAnim != 0) {
             customAnim = true;
             mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim);
             mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim);
+            mRotateAlphaAnimation = AnimationUtils.loadAnimation(mContext,
+                    R.anim.screen_rotate_alpha);
         } else {
             customAnim = false;
-            switch (delta) {
+            switch (delta) { /* Counter-Clockwise Rotations */
                 case Surface.ROTATION_0:
                     mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            com.android.internal.R.anim.screen_rotate_0_exit);
+                            R.anim.screen_rotate_0_exit);
                     mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            com.android.internal.R.anim.screen_rotate_0_enter);
+                            R.anim.screen_rotate_0_enter);
                     break;
                 case Surface.ROTATION_90:
                     mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            com.android.internal.R.anim.screen_rotate_plus_90_exit);
+                            R.anim.screen_rotate_plus_90_exit);
                     mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            com.android.internal.R.anim.screen_rotate_plus_90_enter);
+                            R.anim.screen_rotate_plus_90_enter);
                     break;
                 case Surface.ROTATION_180:
                     mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            com.android.internal.R.anim.screen_rotate_180_exit);
+                            R.anim.screen_rotate_180_exit);
                     mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            com.android.internal.R.anim.screen_rotate_180_enter);
+                            R.anim.screen_rotate_180_enter);
                     break;
                 case Surface.ROTATION_270:
                     mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
-                            com.android.internal.R.anim.screen_rotate_minus_90_exit);
+                            R.anim.screen_rotate_minus_90_exit);
                     mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
-                            com.android.internal.R.anim.screen_rotate_minus_90_enter);
+                            R.anim.screen_rotate_minus_90_enter);
                     break;
             }
         }
 
-        // Initialize the animations.  This is a hack, redefining what "parent"
-        // means to allow supplying the last and next size.  In this definition
-        // "%p" is the original (let's call it "previous") size, and "%" is the
-        // screen's current/new size.
-        mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
         mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
+        mRotateExitAnimation.restrictDuration(maxAnimationDuration);
+        mRotateExitAnimation.scaleCurrentDuration(animationScale);
+        mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
+        mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
+        mRotateEnterAnimation.scaleCurrentDuration(animationScale);
+
         mAnimRunning = false;
         mFinishAnimReady = false;
         mFinishAnimStartTime = -1;
 
-        mRotateExitAnimation.restrictDuration(maxAnimationDuration);
-        mRotateExitAnimation.scaleCurrentDuration(animationScale);
-        mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
-        mRotateEnterAnimation.scaleCurrentDuration(animationScale);
-        mRotateAlphaAnimation.restrictDuration(maxAnimationDuration);
-        mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
-
-        if (!customAnim && mExitingBlackFrame == null) {
-            try {
-                // Compute the transformation matrix that must be applied
-                // the the black frame to make it stay in the initial position
-                // before the new screen rotation.  This is different than the
-                // snapshot transformation because the snapshot is always based
-                // of the native orientation of the screen, not the orientation
-                // we were last in.
-                createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix);
-
-                final Rect outer;
-                final Rect inner;
-                if (mForceDefaultOrientation) {
-                    // Going from a smaller Display to a larger Display, add curtains to sides
-                    // or top and bottom. Going from a larger to smaller display will result in
-                    // no BlackSurfaces being constructed.
-                    outer = mCurrentDisplayRect;
-                    inner = mOriginalDisplayRect;
-                } else {
-                    outer = new Rect(-mWidth, -mHeight, mWidth * 2, mHeight * 2);
-                    inner = new Rect(0, 0, mWidth, mHeight);
-                }
-                mExitingBlackFrame = new BlackFrame(mService.mTransactionFactory, t, outer, inner,
-                        SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation,
-                        mRotationLayer);
-            } catch (OutOfResourcesException e) {
-                Slog.w(TAG, "Unable to allocate black surface", e);
-            }
+        if (customAnim) {
+            mRotateAlphaAnimation.restrictDuration(maxAnimationDuration);
+            mRotateAlphaAnimation.scaleCurrentDuration(animationScale);
         }
 
         if (customAnim && mEnteringBlackFrame == null) {
@@ -451,7 +398,12 @@
             }
         }
 
-        mSurfaceRotationAnimationController.startAnimation();
+        if (customAnim) {
+            mSurfaceRotationAnimationController.startCustomAnimation();
+        } else {
+            mSurfaceRotationAnimationController.startScreenRotationAnimation();
+        }
+
         return true;
     }
 
@@ -460,11 +412,13 @@
      */
     public boolean dismiss(SurfaceControl.Transaction t, long maxAnimationDuration,
             float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
-        if (mSurfaceControl == null) {
+        if (mScreenshotLayer == null) {
             // Can't do animation.
             return false;
         }
         if (!mStarted) {
+            mEndLuma = RotationAnimationUtils.getLumaOfSurfaceControl(mDisplayContent.getDisplay(),
+                    mDisplayContent.getWindowingLayer());
             startAnimation(t, maxAnimationDuration, animationScale, finalWidth, finalHeight,
                     exitAnim, enterAnim);
         }
@@ -480,28 +434,28 @@
             mSurfaceRotationAnimationController.cancel();
             mSurfaceRotationAnimationController = null;
         }
-        if (mSurfaceControl != null) {
-            ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "  FREEZE %s: DESTROY", mSurfaceControl);
-            mSurfaceControl = null;
+
+        if (mScreenshotLayer != null) {
+            ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "  FREEZE %s: DESTROY", mScreenshotLayer);
             SurfaceControl.Transaction t = mService.mTransactionFactory.get();
-            if (mRotationLayer != null) {
-                if (mRotationLayer.isValid()) {
-                    t.remove(mRotationLayer);
-                }
-                mRotationLayer = null;
+            if (mScreenshotLayer.isValid()) {
+                t.remove(mScreenshotLayer);
             }
+            mScreenshotLayer = null;
+
             if (mEnterBlackFrameLayer != null) {
                 if (mEnterBlackFrameLayer.isValid()) {
                     t.remove(mEnterBlackFrameLayer);
                 }
                 mEnterBlackFrameLayer = null;
             }
+            if (mBackColorSurface != null) {
+                t.remove(mBackColorSurface);
+                mBackColorSurface = null;
+            }
             t.apply();
         }
-        if (mExitingBlackFrame != null) {
-            mExitingBlackFrame.kill();
-            mExitingBlackFrame = null;
-        }
+
         if (mEnteringBlackFrame != null) {
             mEnteringBlackFrame.kill();
             mEnteringBlackFrame = null;
@@ -537,18 +491,28 @@
      * Utility class that runs a {@link ScreenRotationAnimation} on the {@link
      * SurfaceAnimationRunner}.
      * <p>
-     * The rotation animation is divided into the following hierarchy:
+     * The rotation animation supports both screen rotation and custom animations
+     *
+     * For custom animations:
      * <ul>
-     * <li> A first rotation layer, containing the blackframes. This layer is animated by the
-     * "screen_rotate_X_exit" that applies a scale and rotate and where X is value of the rotation.
-     *     <ul>
-     *         <li> A child layer containing the screenshot on which is added an animation of it's
-     *     alpha channel ("screen_rotate_alpha") and that will rotate with his parent layer.</li>
-     *     </ul>
-     * <li> A second rotation layer used when custom animations are passed in
+     *   <li>
+     *     The screenshot layer which has an added animation of it's alpha channel
+     *     ("screen_rotate_alpha") and that will be applied along with the custom animation.
+     *   </li>
+     *   <li> A device layer that is animated with the provided custom animation </li>
+     * </ul>
+     *
+     * For screen rotation:
+     * <ul>
+     *   <li> A rotation layer that is both rotated and faded out during a single animation </li>
+     *   <li> A device layer that is both rotated and faded in during a single animation </li>
+     *   <li> A background color layer that transitions colors behind the first two layers </li>
+     * </ul>
+     *
      * {@link ScreenRotationAnimation#startAnimation(
      *     SurfaceControl.Transaction, long, float, int, int, int, int)}.
      * </ul>
+     *
      * <p>
      * Thus an {@link LocalAnimationAdapter.AnimationSpec} is created for each of
      * this three {@link SurfaceControl}s which then delegates the animation to the
@@ -556,22 +520,35 @@
      */
     class SurfaceRotationAnimationController {
         private SurfaceAnimator mDisplayAnimator;
-        private SurfaceAnimator mEnterBlackFrameAnimator;
         private SurfaceAnimator mScreenshotRotationAnimator;
         private SurfaceAnimator mRotateScreenAnimator;
+        private SurfaceAnimator mEnterBlackFrameAnimator;
+
+        void startCustomAnimation() {
+            try {
+                mService.mSurfaceAnimationRunner.deferStartingAnimations();
+                mRotateScreenAnimator = startScreenshotAlphaAnimation();
+                mDisplayAnimator = startDisplayRotation();
+                if (mEnteringBlackFrame != null) {
+                    mEnterBlackFrameAnimator = startEnterBlackFrameAnimation();
+                }
+            } finally {
+                mService.mSurfaceAnimationRunner.continueStartingAnimations();
+            }
+        }
 
         /**
          * Start the rotation animation of the display and the screenshot on the
          * {@link SurfaceAnimationRunner}.
          */
-        void startAnimation() {
-            mRotateScreenAnimator = startScreenshotAlphaAnimation();
-            mDisplayAnimator = startDisplayRotation();
-            if (mExitingBlackFrame != null) {
+        void startScreenRotationAnimation() {
+            try {
+                mService.mSurfaceAnimationRunner.deferStartingAnimations();
+                mDisplayAnimator = startDisplayRotation();
                 mScreenshotRotationAnimator = startScreenshotRotationAnimation();
-            }
-            if (mEnteringBlackFrame != null) {
-                mEnterBlackFrameAnimator = startEnterBlackFrameAnimation();
+                startColorAnimation();
+            } finally {
+                mService.mSurfaceAnimationRunner.continueStartingAnimations();
             }
         }
 
@@ -596,8 +573,8 @@
 
         private SurfaceAnimator startScreenshotAlphaAnimation() {
             return startAnimation(initializeBuilder()
-                            .setSurfaceControl(mSurfaceControl)
-                            .setAnimationLeashParent(mRotationLayer)
+                            .setSurfaceControl(mScreenshotLayer)
+                            .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
                             .setWidth(mWidth)
                             .setHeight(mHeight)
                             .build(),
@@ -616,13 +593,67 @@
 
         private SurfaceAnimator startScreenshotRotationAnimation() {
             return startAnimation(initializeBuilder()
-                            .setSurfaceControl(mRotationLayer)
+                            .setSurfaceControl(mScreenshotLayer)
                             .setAnimationLeashParent(mDisplayContent.getOverlayLayer())
                             .build(),
                     createWindowAnimationSpec(mRotateExitAnimation),
                     this::onAnimationEnd);
         }
 
+
+        /**
+         * Applies the color change from {@link #mStartLuma} to {@link #mEndLuma} as a
+         * grayscale color
+         */
+        private void startColorAnimation() {
+            int colorTransitionMs = mContext.getResources().getInteger(
+                    R.integer.config_screen_rotation_color_transition);
+            final SurfaceAnimationRunner runner = mService.mSurfaceAnimationRunner;
+            final float[] rgbTmpFloat = new float[3];
+            final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
+            final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
+            final long duration = colorTransitionMs * (long) mService.getCurrentAnimatorScale();
+            final ArgbEvaluator va = ArgbEvaluator.getInstance();
+            runner.startAnimation(
+                new LocalAnimationAdapter.AnimationSpec() {
+                    @Override
+                    public long getDuration() {
+                        return duration;
+                    }
+
+                    @Override
+                    public void apply(SurfaceControl.Transaction t, SurfaceControl leash,
+                        long currentPlayTime) {
+                        float fraction = (float)currentPlayTime / (float)getDuration();
+                        int color = (Integer) va.evaluate(fraction, startColor, endColor);
+                        Color middleColor = Color.valueOf(color);
+                        rgbTmpFloat[0] = middleColor.red();
+                        rgbTmpFloat[1] = middleColor.green();
+                        rgbTmpFloat[2] = middleColor.blue();
+                        if (leash.isValid()) {
+                            t.setColor(leash, rgbTmpFloat);
+                        }
+                    }
+
+                    @Override
+                    public void dump(PrintWriter pw, String prefix) {
+                        pw.println(prefix + "startLuma=" + mStartLuma
+                                + " endLuma=" + mEndLuma
+                                + " durationMs=" + colorTransitionMs);
+                    }
+
+                    @Override
+                    public void dumpDebugInner(ProtoOutputStream proto) {
+                        final long token = proto.start(ROTATE);
+                        proto.write(START_LUMA, mStartLuma);
+                        proto.write(END_LUMA, mEndLuma);
+                        proto.write(DURATION_MS, colorTransitionMs);
+                        proto.end(token);
+                    }
+                },
+                mBackColorSurface, mDisplayContent.getPendingTransaction(), null);
+        }
+
         private WindowAnimationSpec createWindowAnimationSpec(Animation mAnimation) {
             return new WindowAnimationSpec(mAnimation, new Point(0, 0) /* position */,
                     false /* canSkipFirstFrame */, 0 /* WindowCornerRadius */);
@@ -646,7 +677,6 @@
 
             LocalAnimationAdapter localAnimationAdapter = new LocalAnimationAdapter(
                     animationSpec, mService.mSurfaceAnimationRunner);
-
             animator.startAnimation(mDisplayContent.getPendingTransaction(),
                     localAnimationAdapter, false);
             return animator;
@@ -692,7 +722,6 @@
             if (mEnterBlackFrameAnimator != null) {
                 mEnterBlackFrameAnimator.cancelAnimation();
             }
-
             if (mScreenshotRotationAnimator != null) {
                 mScreenshotRotationAnimator.cancelAnimation();
             }
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 50cea2e..5633b6b 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -78,6 +78,10 @@
     @GuardedBy("mLock")
     private boolean mAnimationStartDeferred;
 
+    /**
+     * There should only ever be one instance of this class. Usual spot for it is with
+     * {@link WindowManagerService}
+     */
     SurfaceAnimationRunner(Supplier<Transaction> transactionFactory,
             PowerManagerInternal powerManagerInternal) {
         this(null /* callbackProvider */, null /* animatorFactory */,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4e768da..96bc8e9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -792,8 +792,9 @@
         mSeq = seq;
         mPowerManagerWrapper = powerManagerWrapper;
         mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
-        mRequestedInsetsState =
-                getDisplayContent().getInsetsPolicy().getInsetsForDispatch(this);
+        mRequestedInsetsState = new InsetsState(
+                getDisplayContent().getInsetsPolicy().getInsetsForDispatch(this),
+                true /* copySources */);
         if (DEBUG) {
             Slog.v(TAG, "Window " + this + " client=" + c.asBinder()
                             + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
diff --git a/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java
new file mode 100644
index 0000000..94f6676
--- /dev/null
+++ b/services/core/java/com/android/server/wm/utils/RotationAnimationUtils.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+
+/** Helper functions for the {@link com.android.server.wm.ScreenRotationAnimation} class*/
+public class RotationAnimationUtils {
+
+    /**
+     * Converts the provided {@link GraphicBuffer} and converts it to a bitmap to then sample the
+     * luminance at the borders of the bitmap
+     * @return the average luminance of all the pixels at the borders of the bitmap
+     */
+    public static float getAvgBorderLuma(GraphicBuffer graphicBuffer, ColorSpace colorSpace) {
+        Bitmap hwBitmap = Bitmap.wrapHardwareBuffer(graphicBuffer, colorSpace);
+        if (hwBitmap == null) {
+            return 0;
+        }
+
+        Bitmap swaBitmap = hwBitmap.copy(Bitmap.Config.ARGB_8888, false);
+        float totalLuma = 0;
+        int height = swaBitmap.getHeight();
+        int width = swaBitmap.getWidth();
+        int i;
+        for (i = 0; i < width; i++) {
+            totalLuma += swaBitmap.getColor(i, 0).luminance();
+            totalLuma += swaBitmap.getColor(i, height - 1).luminance();
+        }
+        for (i = 0; i < height; i++) {
+            totalLuma += swaBitmap.getColor(0, i).luminance();
+            totalLuma += swaBitmap.getColor(width - 1, i).luminance();
+        }
+        return totalLuma / (2 * width + 2 * height);
+    }
+
+    /**
+     * Gets the average border luma by taking a screenshot of the {@param surfaceControl}.
+     * @see #getAvgBorderLuma(GraphicBuffer, ColorSpace)
+     */
+    public static float getLumaOfSurfaceControl(Display display, SurfaceControl surfaceControl) {
+        if (surfaceControl ==  null) {
+            return 0;
+        }
+
+        Point size = new Point();
+        display.getSize(size);
+        Rect crop = new Rect(0, 0, size.x, size.y);
+        SurfaceControl.ScreenshotGraphicBuffer buffer =
+                SurfaceControl.captureLayers(surfaceControl, crop, 1);
+        return RotationAnimationUtils.getAvgBorderLuma(buffer.getGraphicBuffer(),
+                buffer.getColorSpace());
+    }
+
+    public static void createRotationMatrix(int rotation, int width, int height, Matrix outMatrix) {
+        switch (rotation) {
+            case Surface.ROTATION_0:
+                outMatrix.reset();
+                break;
+            case Surface.ROTATION_90:
+                outMatrix.setRotate(90, 0, 0);
+                outMatrix.postTranslate(height, 0);
+                break;
+            case Surface.ROTATION_180:
+                outMatrix.setRotate(180, 0, 0);
+                outMatrix.postTranslate(width, height);
+                break;
+            case Surface.ROTATION_270:
+                outMatrix.setRotate(270, 0, 0);
+                outMatrix.postTranslate(0, width);
+                break;
+        }
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 5a1d552..8641059 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -64,4 +64,8 @@
     }
 
     public void setLocationEnabled(ComponentName who, boolean locationEnabled) {}
+
+    public boolean isOrganizationOwnedDeviceWithManagedProfile() {
+        return false;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 398ce50..2a08f5c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -367,6 +367,8 @@
 
     private static final String TAG_TRANSFER_OWNERSHIP_BUNDLE = "transfer-ownership-bundle";
 
+    private static final String TAG_PROTECTED_PACKAGES = "protected-packages";
+
     private static final int REQUEST_EXPIRE_PASSWORD = 5571;
 
     private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1);
@@ -742,6 +744,9 @@
         // This is the list of component allowed to start lock task mode.
         List<String> mLockTaskPackages = new ArrayList<>();
 
+        // List of packages protected by device owner
+        List<String> mProtectedPackages = new ArrayList<>();
+
         // Bitfield of feature flags to be enabled during LockTask mode.
         // We default on the power button menu, in order to be consistent with pre-P behaviour.
         int mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
@@ -3245,6 +3250,13 @@
                 out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT);
             }
 
+            for (int i = 0, size = policy.mProtectedPackages.size(); i < size; i++) {
+                String packageName = policy.mProtectedPackages.get(i);
+                out.startTag(null, TAG_PROTECTED_PACKAGES);
+                out.attribute(null, ATTR_NAME, packageName);
+                out.endTag(null, TAG_PROTECTED_PACKAGES);
+            }
+
             out.endTag(null, "policies");
 
             out.endDocument();
@@ -3358,6 +3370,7 @@
             policy.mAdminMap.clear();
             policy.mAffiliationIds.clear();
             policy.mOwnerInstalledCaCerts.clear();
+            policy.mProtectedPackages.clear();
             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
@@ -3455,6 +3468,8 @@
                     policy.mCurrentInputMethodSet = true;
                 } else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
                     policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
+                } else if (TAG_PROTECTED_PACKAGES.equals(tag)) {
+                    policy.mProtectedPackages.add(parser.getAttributeValue(null, ATTR_NAME));
                 } else {
                     Slog.w(LOG_TAG, "Unknown tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -3486,6 +3501,7 @@
         updateMaximumTimeToLockLocked(userHandle);
         updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
         updateLockTaskFeaturesLocked(policy.mLockTaskFeatures, userHandle);
+        updateProtectedPackagesLocked(policy.mProtectedPackages);
         if (policy.mStatusBarDisabled) {
             setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle);
         }
@@ -3511,6 +3527,10 @@
         }
     }
 
+    private void updateProtectedPackagesLocked(List<String> packages) {
+        mInjector.getPackageManagerInternal().setDeviceOwnerProtectedPackages(packages);
+    }
+
     private void updateLockTaskFeaturesLocked(int flags, int userId) {
         long ident = mInjector.binderClearCallingIdentity();
         try {
@@ -8349,6 +8369,8 @@
         policy.mLockTaskPackages.clear();
         updateLockTaskPackagesLocked(policy.mLockTaskPackages, userId);
         policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+        policy.mProtectedPackages.clear();
+        updateProtectedPackagesLocked(policy.mProtectedPackages);
         saveSettingsLocked(userId);
 
         try {
@@ -8585,6 +8607,24 @@
     }
 
     @Override
+    public boolean isOrganizationOwnedDeviceWithManagedProfile() {
+        if (!mHasFeature) {
+            return false;
+        }
+        enforceManageUsers();
+
+        return mInjector.binderWithCleanCallingIdentity(() -> {
+            for (UserInfo ui : mUserManager.getUsers()) {
+                if (ui.isManagedProfile() && isProfileOwnerOfOrganizationOwnedDevice(ui.id)) {
+                    return true;
+                }
+            }
+
+            return false;
+        });
+    }
+
+    @Override
     public boolean checkDeviceIdentifierAccess(String packageName, int pid, int uid) {
         ensureCallerIdentityMatchesIfNotSystem(packageName, pid, uid);
 
@@ -9065,6 +9105,10 @@
             pw.increaseIndent();
             pw.print("mPasswordOwner="); pw.println(policy.mPasswordOwner);
             pw.decreaseIndent();
+            pw.println();
+            pw.increaseIndent();
+            pw.print("mProtectedPackages="); pw.println(policy.mProtectedPackages);
+            pw.decreaseIndent();
         }
     }
 
@@ -14558,8 +14602,10 @@
             for (int i = 0; i < users.length; i++) {
                 final ComponentName componentName = getProfileOwner(users[i]);
                 if (componentName != null) {
-                    admins.add(getActiveAdminForCallerLocked(
-                            componentName, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER));
+                    ActiveAdmin admin = getActiveAdminUncheckedLocked(componentName, users[i]);
+                    if (admin != null) {
+                        admins.add(admin);
+                    }
                 }
             }
             return admins;
@@ -14698,4 +14744,42 @@
         return DevicePolicyConstants.loadFromString(
                 mInjector.settingsGlobalGetString(Global.DEVICE_POLICY_CONSTANTS));
     }
+
+    @Override
+    public void setProtectedPackages(ComponentName who, List<String> packages) {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+        Preconditions.checkNotNull(packages, "packages is null");
+
+        enforceDeviceOwner(who);
+        synchronized (getLockObject()) {
+            final int userHandle = mInjector.userHandleGetCallingUserId();
+            setProtectedPackagesLocked(userHandle, packages);
+            DevicePolicyEventLogger
+                    .createEvent(DevicePolicyEnums.SET_PACKAGES_PROTECTED)
+                    .setAdmin(who)
+                    .setStrings(packages.toArray(new String[packages.size()]))
+                    .write();
+        }
+    }
+
+    private void setProtectedPackagesLocked(int userHandle, List<String> packages) {
+        final DevicePolicyData policy = getUserData(userHandle);
+        policy.mProtectedPackages = packages;
+
+        // Store the settings persistently.
+        saveSettingsLocked(userHandle);
+        updateProtectedPackagesLocked(packages);
+    }
+
+    @Override
+    public List<String> getProtectedPackages(ComponentName who) {
+        Preconditions.checkNotNull(who, "ComponentName is null");
+
+        enforceDeviceOwner(who);
+        final int userHandle = mInjector.binderGetCallingUserHandle().getIdentifier();
+        synchronized (getLockObject()) {
+            final List<String> packages = getUserData(userHandle).mProtectedPackages;
+            return packages == null ? Collections.EMPTY_LIST : packages;
+        }
+    }
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 08221f9..bfec51c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -126,6 +126,7 @@
 import com.android.server.os.BugreportManagerService;
 import com.android.server.os.DeviceIdentifiersPolicyService;
 import com.android.server.os.SchedulingPolicyService;
+import com.android.server.people.PeopleService;
 import com.android.server.pm.BackgroundDexOptService;
 import com.android.server.pm.CrossProfileAppsService;
 import com.android.server.pm.DataLoaderManagerService;
@@ -1917,6 +1918,10 @@
             t.traceBegin("StartCrossProfileAppsService");
             mSystemServiceManager.startService(CrossProfileAppsService.class);
             t.traceEnd();
+
+            t.traceBegin("StartPeopleService");
+            mSystemServiceManager.startService(PeopleService.class);
+            t.traceEnd();
         }
 
         if (!isWatch) {
diff --git a/services/people/Android.bp b/services/people/Android.bp
new file mode 100644
index 0000000..d64097a
--- /dev/null
+++ b/services/people/Android.bp
@@ -0,0 +1,5 @@
+java_library_static {
+    name: "services.people",
+    srcs: ["java/**/*.java"],
+    libs: ["services.core"],
+}
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
new file mode 100644
index 0000000..ef3da60
--- /dev/null
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2019 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.people;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.IPredictionCallback;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * A service that manages the people and conversations provided by apps.
+ */
+public class PeopleService extends SystemService {
+
+    private static final String TAG = "PeopleService";
+
+    /**
+     * Initializes the system service.
+     *
+     * @param context The system server context.
+     */
+    public PeopleService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        publishLocalService(PeopleServiceInternal.class, new LocalService());
+    }
+
+    @VisibleForTesting
+    final class LocalService extends PeopleServiceInternal {
+
+        private Map<AppPredictionSessionId, SessionInfo> mSessions = new ArrayMap<>();
+
+        @Override
+        public void onCreatePredictionSession(AppPredictionContext context,
+                AppPredictionSessionId sessionId) {
+            mSessions.put(sessionId, new SessionInfo(context));
+        }
+
+        @Override
+        public void notifyAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event) {
+            runForSession(sessionId,
+                    sessionInfo -> sessionInfo.getPredictor().onAppTargetEvent(event));
+        }
+
+        @Override
+        public void notifyLaunchLocationShown(AppPredictionSessionId sessionId,
+                String launchLocation, ParceledListSlice targetIds) {
+            runForSession(sessionId,
+                    sessionInfo -> sessionInfo.getPredictor().onLaunchLocationShown(
+                            launchLocation, targetIds.getList()));
+        }
+
+        @Override
+        public void sortAppTargets(AppPredictionSessionId sessionId, ParceledListSlice targets,
+                IPredictionCallback callback) {
+            runForSession(sessionId,
+                    sessionInfo -> sessionInfo.getPredictor().onSortAppTargets(
+                            targets.getList(),
+                            targetList -> invokePredictionCallback(callback, targetList)));
+        }
+
+        @Override
+        public void registerPredictionUpdates(AppPredictionSessionId sessionId,
+                IPredictionCallback callback) {
+            runForSession(sessionId, sessionInfo -> sessionInfo.addCallback(callback));
+        }
+
+        @Override
+        public void unregisterPredictionUpdates(AppPredictionSessionId sessionId,
+                IPredictionCallback callback) {
+            runForSession(sessionId, sessionInfo -> sessionInfo.removeCallback(callback));
+        }
+
+        @Override
+        public void requestPredictionUpdate(AppPredictionSessionId sessionId) {
+            runForSession(sessionId,
+                    sessionInfo -> sessionInfo.getPredictor().onRequestPredictionUpdate());
+        }
+
+        @Override
+        public void onDestroyPredictionSession(AppPredictionSessionId sessionId) {
+            runForSession(sessionId, sessionInfo -> {
+                sessionInfo.onDestroy();
+                mSessions.remove(sessionId);
+            });
+        }
+
+        @VisibleForTesting
+        SessionInfo getSessionInfo(AppPredictionSessionId sessionId) {
+            return mSessions.get(sessionId);
+        }
+
+        private void runForSession(AppPredictionSessionId sessionId, Consumer<SessionInfo> method) {
+            SessionInfo sessionInfo = mSessions.get(sessionId);
+            if (sessionInfo == null) {
+                Slog.e(TAG, "Failed to find the session: " + sessionId);
+                return;
+            }
+            method.accept(sessionInfo);
+        }
+
+        private void invokePredictionCallback(IPredictionCallback callback,
+                List<AppTarget> targets) {
+            try {
+                callback.onResult(new ParceledListSlice<>(targets));
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to calling callback" + e);
+            }
+        }
+    }
+}
diff --git a/services/people/java/com/android/server/people/SessionInfo.java b/services/people/java/com/android/server/people/SessionInfo.java
new file mode 100644
index 0000000..df7cedf
--- /dev/null
+++ b/services/people/java/com/android/server/people/SessionInfo.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 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.people;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppTarget;
+import android.app.prediction.IPredictionCallback;
+import android.content.pm.ParceledListSlice;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.people.prediction.ConversationPredictor;
+
+import java.util.List;
+
+/** Manages the information and callbacks in an app prediction request session. */
+class SessionInfo {
+
+    private static final String TAG = "SessionInfo";
+
+    private final ConversationPredictor mConversationPredictor;
+    private final RemoteCallbackList<IPredictionCallback> mCallbacks =
+            new RemoteCallbackList<>();
+
+    SessionInfo(AppPredictionContext predictionContext) {
+        mConversationPredictor = new ConversationPredictor(predictionContext,
+                this::updatePredictions);
+    }
+
+    void addCallback(IPredictionCallback callback) {
+        mCallbacks.register(callback);
+    }
+
+    void removeCallback(IPredictionCallback callback) {
+        mCallbacks.unregister(callback);
+    }
+
+    ConversationPredictor getPredictor() {
+        return mConversationPredictor;
+    }
+
+    void onDestroy() {
+        mCallbacks.kill();
+    }
+
+    private void updatePredictions(List<AppTarget> targets) {
+        int callbackCount = mCallbacks.beginBroadcast();
+        for (int i = 0; i < callbackCount; i++) {
+            try {
+                mCallbacks.getBroadcastItem(i).onResult(new ParceledListSlice<>(targets));
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to calling callback" + e);
+            }
+        }
+        mCallbacks.finishBroadcast();
+    }
+}
diff --git a/services/people/java/com/android/server/people/prediction/ConversationPredictor.java b/services/people/java/com/android/server/people/prediction/ConversationPredictor.java
new file mode 100644
index 0000000..de71d29
--- /dev/null
+++ b/services/people/java/com/android/server/people/prediction/ConversationPredictor.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 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.people.prediction;
+
+import android.annotation.MainThread;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+/**
+ * Predictor that predicts the conversations or apps the user is most likely to open.
+ */
+public class ConversationPredictor {
+
+    private final AppPredictionContext mPredictionContext;
+    private final Consumer<List<AppTarget>> mUpdatePredictionsMethod;
+    private final ExecutorService mCallbackExecutor;
+
+    public ConversationPredictor(AppPredictionContext predictionContext,
+            Consumer<List<AppTarget>> updatePredictionsMethod) {
+        mPredictionContext = predictionContext;
+        mUpdatePredictionsMethod = updatePredictionsMethod;
+        mCallbackExecutor = Executors.newSingleThreadExecutor();
+    }
+
+    /**
+     * Called by the client app to indicate a target launch.
+     */
+    @MainThread
+    public void onAppTargetEvent(AppTargetEvent event) {
+    }
+
+    /**
+     * Called by the client app to indicate a particular location has been shown to the user.
+     */
+    @MainThread
+    public void onLaunchLocationShown(String launchLocation, List<AppTargetId> targetIds) {
+    }
+
+    /**
+     * Called by the client app to request sorting of the provided targets based on the prediction
+     * ranking.
+     */
+    @MainThread
+    public void onSortAppTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) {
+        mCallbackExecutor.execute(() -> callback.accept(targets));
+    }
+
+    /**
+     * Called by the client app to request target predictions.
+     */
+    @MainThread
+    public void onRequestPredictionUpdate() {
+        List<AppTarget> targets = new ArrayList<>();
+        mCallbackExecutor.execute(() -> mUpdatePredictionsMethod.accept(targets));
+    }
+
+    @VisibleForTesting
+    public Consumer<List<AppTarget>> getUpdatePredictionsMethod() {
+        return mUpdatePredictionsMethod;
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 0504d14..067f23a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -16,6 +16,8 @@
 package com.android.server.appop;
 
 import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.MODE_FOREGROUND;
@@ -287,7 +289,7 @@
         mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null);
 
         AppOpsManager.HistoricalOps historicalOps = new AppOpsManager.HistoricalOps(0, 15000);
-        historicalOps.increaseAccessCount(OP_READ_SMS, mMyUid, sMyPackageName,
+        historicalOps.increaseAccessCount(OP_READ_SMS, mMyUid, sMyPackageName, null,
                 AppOpsManager.UID_STATE_PERSISTENT, 0, 1);
 
         mAppOpsService.addHistoricalOps(historicalOps);
@@ -300,8 +302,8 @@
         });
 
         // First, do a fetch to ensure it's written
-        mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, 0, Long.MAX_VALUE, 0,
-                callback);
+        mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, null,
+                FILTER_BY_UID | FILTER_BY_PACKAGE_NAME, 0, Long.MAX_VALUE, 0, callback);
 
         latchRef.get().await(5, TimeUnit.SECONDS);
         assertThat(latchRef.get().getCount()).isEqualTo(0);
@@ -312,8 +314,8 @@
 
         latchRef.set(new CountDownLatch(1));
 
-        mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, 0, Long.MAX_VALUE, 0,
-                callback);
+        mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, null,
+                FILTER_BY_UID | FILTER_BY_PACKAGE_NAME, 0, Long.MAX_VALUE, 0, callback);
 
         latchRef.get().await(5, TimeUnit.SECONDS);
         assertThat(latchRef.get().getCount()).isEqualTo(0);
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
new file mode 100644
index 0000000..80aec73
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
@@ -0,0 +1,848 @@
+/*
+ * Copyright (C) 2019 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.utils.quota;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.utils.quota.Category.SINGLE_CATEGORY;
+import static com.android.server.utils.quota.QuotaTracker.MAX_WINDOW_SIZE_MS;
+import static com.android.server.utils.quota.QuotaTracker.MIN_WINDOW_SIZE_MS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.LongArrayQueue;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.utils.quota.CountQuotaTracker.ExecutionStats;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+/**
+ * Tests for {@link CountQuotaTracker}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CountQuotaTrackerTest {
+    private static final long SECOND_IN_MILLIS = 1000L;
+    private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
+    private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
+    private static final String TAG_CLEANUP = "*CountQuotaTracker.cleanup*";
+    private static final String TAG_QUOTA_CHECK = "*QuotaTracker.quota_check*";
+    private static final String TEST_PACKAGE = "com.android.frameworks.mockingservicestests";
+    private static final String TEST_TAG = "testing";
+    private static final int TEST_UID = 10987;
+    private static final int TEST_USER_ID = 0;
+
+    /** A {@link Category} to represent the ACTIVE standby bucket. */
+    private static final Category ACTIVE_BUCKET_CATEGORY = new Category("ACTIVE");
+
+    /** A {@link Category} to represent the WORKING_SET standby bucket. */
+    private static final Category WORKING_SET_BUCKET_CATEGORY = new Category("WORKING_SET");
+
+    /** A {@link Category} to represent the FREQUENT standby bucket. */
+    private static final Category FREQUENT_BUCKET_CATEGORY = new Category("FREQUENT");
+
+    /** A {@link Category} to represent the RARE standby bucket. */
+    private static final Category RARE_BUCKET_CATEGORY = new Category("RARE");
+
+    private CountQuotaTracker mQuotaTracker;
+    private final CategorizerForTest mCategorizer = new CategorizerForTest();
+    private final InjectorForTest mInjector = new InjectorForTest();
+    private final TestQuotaChangeListener mQuotaChangeListener = new TestQuotaChangeListener();
+    private BroadcastReceiver mReceiver;
+    private MockitoSession mMockingSession;
+    @Mock
+    private AlarmManager mAlarmManager;
+    @Mock
+    private Context mContext;
+
+    static class CategorizerForTest implements Categorizer {
+        private Category mCategoryToUse = SINGLE_CATEGORY;
+
+        @Override
+        public Category getCategory(int userId,
+                String packageName, String tag) {
+            return mCategoryToUse;
+        }
+    }
+
+    private static class InjectorForTest extends QuotaTracker.Injector {
+        private long mElapsedTime = SystemClock.elapsedRealtime();
+
+        @Override
+        long getElapsedRealtime() {
+            return mElapsedTime;
+        }
+
+        @Override
+        boolean isAlarmManagerReady() {
+            return true;
+        }
+    }
+
+    private static class TestQuotaChangeListener implements QuotaChangeListener {
+
+        @Override
+        public void onQuotaStateChanged(int userId, String packageName, String tag) {
+
+        }
+    }
+
+    @Before
+    public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .strictness(Strictness.LENIENT)
+                .mockStatic(LocalServices.class)
+                .startMocking();
+
+        when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+        when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
+
+        // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
+        // in the past, and QuotaController sometimes floors values at 0, so if the test time
+        // causes sessions with negative timestamps, they will fail.
+        advanceElapsedClock(24 * HOUR_IN_MILLIS);
+
+        // Initialize real objects.
+        // Capture the listeners.
+        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        mQuotaTracker = new CountQuotaTracker(mContext, mCategorizer, mInjector);
+        mQuotaTracker.setEnabled(true);
+        mQuotaTracker.setQuotaFree(false);
+        mQuotaTracker.registerQuotaChangeListener(mQuotaChangeListener);
+        verify(mContext, atLeastOnce()).registerReceiverAsUser(
+                receiverCaptor.capture(), eq(UserHandle.ALL), any(), any(), any());
+        mReceiver = receiverCaptor.getValue();
+    }
+
+    @After
+    public void tearDown() {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    /**
+     * Returns true if the two {@link LongArrayQueue}s have the same size and the same elements in
+     * the same order.
+     */
+    private static boolean longArrayQueueEquals(LongArrayQueue queue1, LongArrayQueue queue2) {
+        if (queue1 == queue2) {
+            return true;
+        } else if (queue1 == null || queue2 == null) {
+            return false;
+        }
+        if (queue1.size() == queue2.size()) {
+            for (int i = 0; i < queue1.size(); ++i) {
+                if (queue1.get(i) != queue2.get(i)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void advanceElapsedClock(long incrementMs) {
+        mInjector.mElapsedTime += incrementMs;
+    }
+
+    private void logEvents(int count) {
+        logEvents(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, count);
+    }
+
+    private void logEvents(int userId, String pkgName, String tag, int count) {
+        for (int i = 0; i < count; ++i) {
+            mQuotaTracker.noteEvent(userId, pkgName, tag);
+        }
+    }
+
+    private void logEventAt(long timeElapsed) {
+        logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, timeElapsed);
+    }
+
+    private void logEventAt(int userId, String pkgName, String tag, long timeElapsed) {
+        long now = mInjector.getElapsedRealtime();
+        mInjector.mElapsedTime = timeElapsed;
+        mQuotaTracker.noteEvent(userId, pkgName, tag);
+        mInjector.mElapsedTime = now;
+    }
+
+    private void logEventsAt(int userId, String pkgName, String tag, long timeElapsed, int count) {
+        for (int i = 0; i < count; ++i) {
+            logEventAt(userId, pkgName, tag, timeElapsed);
+        }
+    }
+
+    @Test
+    public void testDeleteObsoleteEventsLocked() {
+        // Count window size should only apply to event list.
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 7, 2 * HOUR_IN_MILLIS);
+
+        final long now = mInjector.getElapsedRealtime();
+
+        logEventAt(now - 6 * HOUR_IN_MILLIS);
+        logEventAt(now - 5 * HOUR_IN_MILLIS);
+        logEventAt(now - 4 * HOUR_IN_MILLIS);
+        logEventAt(now - 3 * HOUR_IN_MILLIS);
+        logEventAt(now - 2 * HOUR_IN_MILLIS);
+        logEventAt(now - HOUR_IN_MILLIS);
+        logEventAt(now - 1);
+
+        LongArrayQueue expectedEvents = new LongArrayQueue();
+        expectedEvents.addLast(now - HOUR_IN_MILLIS);
+        expectedEvents.addLast(now - 1);
+
+        mQuotaTracker.deleteObsoleteEventsLocked();
+
+        LongArrayQueue remainingEvents = mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE,
+                TEST_TAG);
+        assertTrue(longArrayQueueEquals(expectedEvents, remainingEvents));
+    }
+
+    @Test
+    public void testAppRemoval() {
+        final long now = mInjector.getElapsedRealtime();
+        logEventAt(TEST_USER_ID, "com.android.test.remove", "tag1", now - (6 * HOUR_IN_MILLIS));
+        logEventAt(TEST_USER_ID, "com.android.test.remove", "tag2",
+                now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS));
+        logEventAt(TEST_USER_ID, "com.android.test.remove", "tag3", now - (HOUR_IN_MILLIS));
+        // Test that another app isn't affected.
+        LongArrayQueue expected1 = new LongArrayQueue();
+        expected1.addLast(now - 10 * MINUTE_IN_MILLIS);
+        LongArrayQueue expected2 = new LongArrayQueue();
+        expected2.addLast(now - 70 * MINUTE_IN_MILLIS);
+        logEventAt(TEST_USER_ID, "com.android.test.stay", "tag1", now - 10 * MINUTE_IN_MILLIS);
+        logEventAt(TEST_USER_ID, "com.android.test.stay", "tag2", now - 70 * MINUTE_IN_MILLIS);
+
+        Intent removal = new Intent(Intent.ACTION_PACKAGE_FULLY_REMOVED,
+                Uri.fromParts("package", "com.android.test.remove", null));
+        removal.putExtra(Intent.EXTRA_UID, TEST_UID);
+        mReceiver.onReceive(mContext, removal);
+        assertNull(
+                mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag1"));
+        assertNull(
+                mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag2"));
+        assertNull(
+                mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag3"));
+        assertTrue(longArrayQueueEquals(expected1,
+                mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.stay", "tag1")));
+        assertTrue(longArrayQueueEquals(expected2,
+                mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.stay", "tag2")));
+    }
+
+    @Test
+    public void testUserRemoval() {
+        final long now = mInjector.getElapsedRealtime();
+        logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag1", now - (6 * HOUR_IN_MILLIS));
+        logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag2",
+                now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS));
+        logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag3", now - (HOUR_IN_MILLIS));
+        // Test that another user isn't affected.
+        LongArrayQueue expected = new LongArrayQueue();
+        expected.addLast(now - (70 * MINUTE_IN_MILLIS));
+        expected.addLast(now - (10 * MINUTE_IN_MILLIS));
+        logEventAt(10, TEST_PACKAGE, "tag4", now - (70 * MINUTE_IN_MILLIS));
+        logEventAt(10, TEST_PACKAGE, "tag4", now - 10 * MINUTE_IN_MILLIS);
+
+        Intent removal = new Intent(Intent.ACTION_USER_REMOVED);
+        removal.putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID);
+        mReceiver.onReceive(mContext, removal);
+        assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag1"));
+        assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag2"));
+        assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag3"));
+        longArrayQueueEquals(expected, mQuotaTracker.getEvents(10, TEST_PACKAGE, "tag4"));
+    }
+
+    @Test
+    public void testUpdateExecutionStatsLocked_NoTimer() {
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 3, 24 * HOUR_IN_MILLIS);
+        final long now = mInjector.getElapsedRealtime();
+
+        // Added in chronological order.
+        logEventAt(now - 4 * HOUR_IN_MILLIS);
+        logEventAt(now - HOUR_IN_MILLIS);
+        logEventAt(now - 5 * MINUTE_IN_MILLIS);
+        logEventAt(now - MINUTE_IN_MILLIS);
+
+        // Test an app that hasn't had any activity.
+        ExecutionStats expectedStats = new ExecutionStats();
+        ExecutionStats inputStats = new ExecutionStats();
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
+        inputStats.countLimit = expectedStats.countLimit = 3;
+        // Invalid time is now +24 hours since there are no sessions at all for the app.
+        expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, "com.android.test.not.run", TEST_TAG,
+                inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        // Now test app that has had activity.
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
+        // Invalid time is now since there was an event exactly windowSizeMs ago.
+        expectedStats.expirationTimeElapsed = now;
+        expectedStats.countInWindow = 1;
+        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 2 * MINUTE_IN_MILLIS;
+        expectedStats.countInWindow = 1;
+        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 4 * MINUTE_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 3 * MINUTE_IN_MILLIS;
+        expectedStats.countInWindow = 1;
+        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
+        // Invalid time is now +44 minutes since the earliest session in the window is now-5
+        // minutes.
+        expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
+        expectedStats.countInWindow = 2;
+        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 45 * MINUTE_IN_MILLIS;
+        expectedStats.countInWindow = 2;
+        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
+        // Invalid time is now since the event is at the very edge of the window
+        // cutoff time.
+        expectedStats.expirationTimeElapsed = now;
+        expectedStats.countInWindow = 3;
+        // App is at event count limit but the oldest session is at the edge of the window, so
+        // in quota time is now.
+        expectedStats.inQuotaTimeElapsed = now;
+        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+        expectedStats.countInWindow = 3;
+        expectedStats.inQuotaTimeElapsed = now + HOUR_IN_MILLIS;
+        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+        expectedStats.countInWindow = 4;
+        expectedStats.inQuotaTimeElapsed = now + 4 * HOUR_IN_MILLIS;
+        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        assertEquals(expectedStats, inputStats);
+
+        inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
+        expectedStats.expirationTimeElapsed = now + 2 * HOUR_IN_MILLIS;
+        expectedStats.countInWindow = 4;
+        expectedStats.inQuotaTimeElapsed = now + 5 * HOUR_IN_MILLIS;
+        mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+        assertEquals(expectedStats, inputStats);
+    }
+
+    /**
+     * Tests that getExecutionStatsLocked returns the correct stats.
+     */
+    @Test
+    public void testGetExecutionStatsLocked_Values() {
+        // The handler could cause changes to the cached stats, so prevent it from operating in
+        // this test.
+        Handler handler = mQuotaTracker.getHandler();
+        spyOn(handler);
+        doNothing().when(handler).handleMessage(any());
+
+        mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 3, 24 * HOUR_IN_MILLIS);
+        mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 4, 8 * HOUR_IN_MILLIS);
+        mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 9, 2 * HOUR_IN_MILLIS);
+        mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 10, 10 * MINUTE_IN_MILLIS);
+
+        final long now = mInjector.getElapsedRealtime();
+
+        logEventAt(now - 23 * HOUR_IN_MILLIS);
+        logEventAt(now - 7 * HOUR_IN_MILLIS);
+        logEventAt(now - 5 * HOUR_IN_MILLIS);
+        logEventAt(now - 2 * HOUR_IN_MILLIS);
+        logEventAt(now - 5 * MINUTE_IN_MILLIS);
+
+        ExecutionStats expectedStats = new ExecutionStats();
+
+        // Active
+        expectedStats.expirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS;
+        expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
+        expectedStats.countLimit = 10;
+        expectedStats.countInWindow = 1;
+        mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
+        assertEquals(expectedStats,
+                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+
+        // Working
+        expectedStats.expirationTimeElapsed = now;
+        expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+        expectedStats.countLimit = 9;
+        expectedStats.countInWindow = 2;
+        mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
+        assertEquals(expectedStats,
+                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+
+        // Frequent
+        expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+        expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+        expectedStats.countLimit = 4;
+        expectedStats.countInWindow = 4;
+        expectedStats.inQuotaTimeElapsed = now + HOUR_IN_MILLIS;
+        mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
+        assertEquals(expectedStats,
+                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+
+        // Rare
+        expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+        expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+        expectedStats.countLimit = 3;
+        expectedStats.countInWindow = 5;
+        expectedStats.inQuotaTimeElapsed = now + 19 * HOUR_IN_MILLIS;
+        mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
+        assertEquals(expectedStats,
+                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+    }
+
+    /**
+     * Tests that getExecutionStatsLocked returns the correct stats soon after device startup.
+     */
+    @Test
+    public void testGetExecutionStatsLocked_Values_BeginningOfTime() {
+        // Set time to 3 minutes after boot.
+        mInjector.mElapsedTime = 3 * MINUTE_IN_MILLIS;
+
+        logEventAt(30_000);
+        logEventAt(MINUTE_IN_MILLIS);
+        logEventAt(2 * MINUTE_IN_MILLIS);
+
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+        ExecutionStats expectedStats = new ExecutionStats();
+
+        expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+        expectedStats.countLimit = 10;
+        expectedStats.countInWindow = 3;
+        expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + 30_000;
+        assertEquals(expectedStats,
+                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+    }
+
+    @Test
+    public void testisWithinQuota_GlobalQuotaFree() {
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 2 * HOUR_IN_MILLIS);
+        mQuotaTracker.setQuotaFree(true);
+        assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, null));
+        assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.random.app", null));
+    }
+
+    @Test
+    public void testisWithinQuota_UptcQuotaFree() {
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 2 * HOUR_IN_MILLIS);
+        mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, true);
+        assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, null));
+        assertFalse(
+                mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.random.app", null));
+    }
+
+    @Test
+    public void testisWithinQuota_UnderCount() {
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+        logEvents(5);
+        assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+    }
+
+    @Test
+    public void testisWithinQuota_OverCount() {
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 25, HOUR_IN_MILLIS);
+        logEvents(TEST_USER_ID, "com.android.test.spam", TEST_TAG, 30);
+        assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.test.spam", TEST_TAG));
+    }
+
+    @Test
+    public void testisWithinQuota_EqualsCount() {
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 25, HOUR_IN_MILLIS);
+        logEvents(25);
+        assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+    }
+
+    @Test
+    public void testisWithinQuota_DifferentCategories() {
+        mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 3, 24 * HOUR_IN_MILLIS);
+        mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 4, 24 * HOUR_IN_MILLIS);
+        mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 5, 24 * HOUR_IN_MILLIS);
+        mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 6, 24 * HOUR_IN_MILLIS);
+
+        for (int i = 0; i < 7; ++i) {
+            logEvents(1);
+
+            mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
+            assertEquals("Rare has incorrect quota status with " + (i + 1) + " events",
+                    i < 2,
+                    mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+            mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
+            assertEquals("Frequent has incorrect quota status with " + (i + 1) + " events",
+                    i < 3,
+                    mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+            mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
+            assertEquals("Working has incorrect quota status with " + (i + 1) + " events",
+                    i < 4,
+                    mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+            mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
+            assertEquals("Active has incorrect quota status with " + (i + 1) + " events",
+                    i < 5,
+                    mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+        }
+    }
+
+    @Test
+    public void testMaybeScheduleCleanupAlarmLocked() {
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 5, 24 * HOUR_IN_MILLIS);
+
+        // No sessions saved yet.
+        mQuotaTracker.maybeScheduleCleanupAlarmLocked();
+        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any());
+
+        // Test with only one timing session saved.
+        final long now = mInjector.getElapsedRealtime();
+        logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 6 * HOUR_IN_MILLIS);
+        mQuotaTracker.maybeScheduleCleanupAlarmLocked();
+        verify(mAlarmManager, timeout(1000).times(1))
+                .set(anyInt(), eq(now + 18 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
+
+        // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again.
+        logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS);
+        logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS);
+        mQuotaTracker.maybeScheduleCleanupAlarmLocked();
+        verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(now + 18 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
+    }
+
+    /**
+     * Tests that maybeScheduleStartAlarm schedules an alarm for the right time.
+     */
+    @Test
+    public void testMaybeScheduleStartAlarmLocked() {
+        // logEvent calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+        // because it schedules an alarm too. Prevent it from doing so.
+        spyOn(mQuotaTracker);
+        doNothing().when(mQuotaTracker).maybeScheduleCleanupAlarmLocked();
+
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 8 * HOUR_IN_MILLIS);
+
+        // No sessions saved yet.
+        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+        // Test with timing sessions out of window.
+        final long now = mInjector.getElapsedRealtime();
+        logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 10 * HOUR_IN_MILLIS, 20);
+        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+        // Test with timing sessions in window but still in quota.
+        final long start = now - (6 * HOUR_IN_MILLIS);
+        final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS;
+        logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, start, 5);
+        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+        // Add some more sessions, but still in quota.
+        logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS, 1);
+        logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 3);
+        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+        // Test when out of quota.
+        logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 1);
+        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        verify(mAlarmManager, timeout(1000).times(1))
+                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+        // Alarm already scheduled, so make sure it's not scheduled again.
+        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        verify(mAlarmManager, times(1))
+                .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+    }
+
+    /** Tests that the start alarm is properly rescheduled if the app's category is changed. */
+    @Test
+    public void testMaybeScheduleStartAlarmLocked_CategoryChange() {
+        // logEvent calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+        // because it schedules an alarm too. Prevent it from doing so.
+        spyOn(mQuotaTracker);
+        doNothing().when(mQuotaTracker).maybeScheduleCleanupAlarmLocked();
+
+        mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 10, 24 * HOUR_IN_MILLIS);
+        mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 10, 8 * HOUR_IN_MILLIS);
+        mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+        mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 10, 10 * MINUTE_IN_MILLIS);
+
+        final long now = mInjector.getElapsedRealtime();
+
+        // Affects rare bucket
+        logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 12 * HOUR_IN_MILLIS, 9);
+        // Affects frequent and rare buckets
+        logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 4 * HOUR_IN_MILLIS, 4);
+        // Affects working, frequent, and rare buckets
+        final long outOfQuotaTime = now - HOUR_IN_MILLIS;
+        logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, outOfQuotaTime, 7);
+        // Affects all buckets
+        logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 5 * MINUTE_IN_MILLIS, 3);
+
+        InOrder inOrder = inOrder(mAlarmManager);
+
+        // Start in ACTIVE bucket.
+        mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
+        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        inOrder.verify(mAlarmManager, never())
+                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+        inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
+
+        // And down from there.
+        final long expectedWorkingAlarmTime = outOfQuotaTime + (2 * HOUR_IN_MILLIS);
+        mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
+        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1))
+                .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+        final long expectedFrequentAlarmTime = outOfQuotaTime + (8 * HOUR_IN_MILLIS);
+        mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
+        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1))
+                .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+        final long expectedRareAlarmTime = outOfQuotaTime + (24 * HOUR_IN_MILLIS);
+        mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
+        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1))
+                .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+        // And back up again.
+        mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
+        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1))
+                .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+        mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
+        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1))
+                .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+        mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
+        mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        inOrder.verify(mAlarmManager, timeout(1000).times(1))
+                .cancel(any(AlarmManager.OnAlarmListener.class));
+        inOrder.verify(mAlarmManager, timeout(1000).times(0))
+                .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+    }
+
+    @Test
+    public void testConstantsUpdating_ValidValues() {
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 60_000);
+        assertEquals(0, mQuotaTracker.getLimit(SINGLE_CATEGORY));
+        assertEquals(60_000, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY));
+    }
+
+    @Test
+    public void testConstantsUpdating_InvalidValues() {
+        // Test negatives.
+        try {
+            mQuotaTracker.setCountLimit(SINGLE_CATEGORY, -1, 5000);
+            fail("Negative count limit didn't throw an exception");
+        } catch (IllegalArgumentException e) {
+            // Success
+        }
+        try {
+            mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 1, -1);
+            fail("Negative count window size didn't throw an exception");
+        } catch (IllegalArgumentException e) {
+            // Success
+        }
+
+        // Test window sizes too low.
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 1);
+        assertEquals(MIN_WINDOW_SIZE_MS, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY));
+
+        // Test window sizes too high.
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 365 * 24 * HOUR_IN_MILLIS);
+        assertEquals(MAX_WINDOW_SIZE_MS, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY));
+    }
+
+    /** Tests that events aren't counted when global quota is free. */
+    @Test
+    public void testLogEvent_GlobalQuotaFree() {
+        mQuotaTracker.setQuotaFree(true);
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+        ExecutionStats stats =
+                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        assertEquals(0, stats.countInWindow);
+
+        for (int i = 0; i < 10; ++i) {
+            mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+            advanceElapsedClock(10 * SECOND_IN_MILLIS);
+
+            mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+            assertEquals(0, stats.countInWindow);
+        }
+    }
+
+    /**
+     * Tests that events are counted when global quota is not free.
+     */
+    @Test
+    public void testLogEvent_GlobalQuotaNotFree() {
+        mQuotaTracker.setQuotaFree(false);
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+        ExecutionStats stats =
+                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        assertEquals(0, stats.countInWindow);
+
+        for (int i = 0; i < 10; ++i) {
+            mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+            advanceElapsedClock(10 * SECOND_IN_MILLIS);
+
+            mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+            assertEquals(i + 1, stats.countInWindow);
+        }
+    }
+
+    /** Tests that events aren't counted when the uptc quota is free. */
+    @Test
+    public void testLogEvent_UptcQuotaFree() {
+        mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, true);
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+        ExecutionStats stats =
+                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        assertEquals(0, stats.countInWindow);
+
+        for (int i = 0; i < 10; ++i) {
+            mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+            advanceElapsedClock(10 * SECOND_IN_MILLIS);
+
+            mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+            assertEquals(0, stats.countInWindow);
+        }
+    }
+
+    /**
+     * Tests that events are counted when UPTC quota is not free.
+     */
+    @Test
+    public void testLogEvent_UptcQuotaNotFree() {
+        mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, false);
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+        ExecutionStats stats =
+                mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        assertEquals(0, stats.countInWindow);
+
+        for (int i = 0; i < 10; ++i) {
+            mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+            advanceElapsedClock(10 * SECOND_IN_MILLIS);
+
+            mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+            assertEquals(i + 1, stats.countInWindow);
+        }
+    }
+
+    /**
+     * Tests that QuotaChangeListeners are notified when a UPTC reaches its count quota.
+     */
+    @Test
+    public void testTracking_OutOfQuota() {
+        spyOn(mQuotaChangeListener);
+
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+        logEvents(9);
+
+        mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+
+        // Wait for some extra time to allow for processing.
+        verify(mQuotaChangeListener, timeout(3 * SECOND_IN_MILLIS).times(1))
+                .onQuotaStateChanged(eq(TEST_USER_ID), eq(TEST_PACKAGE), eq(TEST_TAG));
+        assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+    }
+
+    /**
+     * Tests that QuotaChangeListeners are not incorrectly notified after a UPTC event is logged
+     * quota times.
+     */
+    @Test
+    public void testTracking_InQuota() {
+        spyOn(mQuotaChangeListener);
+
+        mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 5, MINUTE_IN_MILLIS);
+
+        // Log an event once per minute. This is well below the quota, so listeners should not be
+        // notified.
+        for (int i = 0; i < 10; i++) {
+            advanceElapsedClock(MINUTE_IN_MILLIS);
+            mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+        }
+
+        // Wait for some extra time to allow for processing.
+        verify(mQuotaChangeListener, timeout(3 * SECOND_IN_MILLIS).times(0))
+                .onQuotaStateChanged(eq(TEST_USER_ID), eq(TEST_PACKAGE), eq(TEST_TAG));
+        assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+    }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 1232340..ace15eb 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -26,6 +26,7 @@
         "services.core",
         "services.devicepolicy",
         "services.net",
+        "services.people",
         "services.usage",
         "guava",
         "androidx.test.core",
diff --git a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
index e9c5ce7..d5483ff 100644
--- a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
@@ -19,8 +19,11 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.location.Country;
 import android.location.CountryListener;
 import android.location.ICountryListener;
@@ -31,6 +34,10 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.internal.R;
+import com.android.server.location.ComprehensiveCountryDetector;
+import com.android.server.location.CustomCountryDetectorTestClass;
+
 import com.google.common.truth.Expect;
 
 import org.junit.Before;
@@ -38,12 +45,18 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.Spy;
 import org.mockito.junit.MockitoJUnitRunner;
 
 @RunWith(MockitoJUnitRunner.class)
 public class CountryDetectorServiceTest {
 
+    private static final String VALID_CUSTOM_TEST_CLASS =
+            "com.android.server.location.CustomCountryDetectorTestClass";
+    private static final String INVALID_CUSTOM_TEST_CLASS =
+            "com.android.server.location.MissingCountryDetectorTestClass";
+
     private static class CountryListenerTester extends ICountryListener.Stub {
         private Country mCountry;
 
@@ -83,12 +96,11 @@
         }
     }
 
-    @Rule
-    public final Expect expect = Expect.create();
-    @Spy
-    private Context mContext = ApplicationProvider.getApplicationContext();
-    @Spy
-    private Handler mHandler = new Handler(Looper.myLooper());
+    @Rule public final Expect expect = Expect.create();
+    @Spy private Context mContext = ApplicationProvider.getApplicationContext();
+    @Spy private Handler mHandler = new Handler(Looper.myLooper());
+    @Mock private Resources mResources;
+
     private CountryDetectorServiceTester mCountryDetectorService;
 
     @BeforeClass
@@ -108,10 +120,12 @@
             message.getCallback().run();
             return true;
         }).when(mHandler).sendMessageAtTime(any(Message.class), anyLong());
+
+        doReturn(mResources).when(mContext).getResources();
     }
 
     @Test
-    public void countryListener_add_successful() throws RemoteException {
+    public void addCountryListener_validListener_listenerAdded() throws RemoteException {
         CountryListenerTester countryListener = new CountryListenerTester();
 
         mCountryDetectorService.systemRunning();
@@ -122,7 +136,7 @@
     }
 
     @Test
-    public void countryListener_remove_successful() throws RemoteException {
+    public void removeCountryListener_validListener_listenerRemoved() throws RemoteException {
         CountryListenerTester countryListener = new CountryListenerTester();
 
         mCountryDetectorService.systemRunning();
@@ -133,8 +147,31 @@
         expect.that(mCountryDetectorService.isListenerSet()).isFalse();
     }
 
+    @Test(expected = RemoteException.class)
+    public void addCountryListener_serviceNotReady_throwsException() throws RemoteException {
+        CountryListenerTester countryListener = new CountryListenerTester();
+
+        expect.that(mCountryDetectorService.isSystemReady()).isFalse();
+        mCountryDetectorService.addCountryListener(countryListener);
+    }
+
+    @Test(expected = RemoteException.class)
+    public void removeCountryListener_serviceNotReady_throwsException() throws RemoteException {
+        CountryListenerTester countryListener = new CountryListenerTester();
+
+        expect.that(mCountryDetectorService.isSystemReady()).isFalse();
+        mCountryDetectorService.removeCountryListener(countryListener);
+    }
+
     @Test
-    public void countryListener_notify_successful() throws RemoteException {
+    public void detectCountry_serviceNotReady_returnNull() {
+        expect.that(mCountryDetectorService.isSystemReady()).isFalse();
+
+        expect.that(mCountryDetectorService.detectCountry()).isNull();
+    }
+
+    @Test
+    public void notifyReceivers_twoListenersRegistered_bothNotified() throws RemoteException {
         CountryListenerTester countryListenerA = new CountryListenerTester();
         CountryListenerTester countryListenerB = new CountryListenerTester();
         Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK);
@@ -151,4 +188,26 @@
         expect.that(countryListenerA.getCountry().equalsIgnoreSource(country)).isTrue();
         expect.that(countryListenerB.getCountry().equalsIgnoreSource(country)).isTrue();
     }
+
+    @Test
+    public void initialize_deviceWithCustomDetector_useCustomDetectorClass() {
+        when(mResources.getString(R.string.config_customCountryDetector))
+                .thenReturn(VALID_CUSTOM_TEST_CLASS);
+
+        mCountryDetectorService.initialize();
+
+        expect.that(mCountryDetectorService.getCountryDetector())
+                .isInstanceOf(CustomCountryDetectorTestClass.class);
+    }
+
+    @Test
+    public void initialize_deviceWithInvalidCustomDetector_useDefaultDetector() {
+        when(mResources.getString(R.string.config_customCountryDetector))
+                .thenReturn(INVALID_CUSTOM_TEST_CLASS);
+
+        mCountryDetectorService.initialize();
+
+        expect.that(mCountryDetectorService.getCountryDetector())
+                .isInstanceOf(ComprehensiveCountryDetector.class);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index f3c76b6..8871348 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -28,6 +28,7 @@
 
 import android.app.admin.DevicePolicyManagerInternal;
 import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetManagerInternal;
 import android.appwidget.AppWidgetProviderInfo;
 import android.appwidget.PendingHostUpdate;
 import android.content.BroadcastReceiver;
@@ -80,6 +81,7 @@
         super.setUp();
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
         LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+        LocalServices.removeServiceForTest(AppWidgetManagerInternal.class);
 
         mTestContext = new TestContext();
         mPkgName = mTestContext.getOpPackageName();
diff --git a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
new file mode 100644
index 0000000..d0767cc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 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.compat;
+
+import android.content.pm.ApplicationInfo;
+
+class ApplicationInfoBuilder {
+    private boolean mIsDebuggable;
+    private int mTargetSdk;
+    private String mPackageName;
+
+    private ApplicationInfoBuilder() {
+        mTargetSdk = -1;
+    }
+
+    static ApplicationInfoBuilder create() {
+        return new ApplicationInfoBuilder();
+    }
+
+    ApplicationInfoBuilder withTargetSdk(int targetSdk) {
+        mTargetSdk = targetSdk;
+        return this;
+    }
+
+    ApplicationInfoBuilder debuggable() {
+        mIsDebuggable = true;
+        return this;
+    }
+
+    ApplicationInfoBuilder withPackageName(String packageName) {
+        mPackageName = packageName;
+        return this;
+    }
+
+    ApplicationInfo build() {
+        final ApplicationInfo applicationInfo = new ApplicationInfo();
+        if (mIsDebuggable) {
+            applicationInfo.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+        }
+        applicationInfo.packageName = mPackageName;
+        applicationInfo.targetSdkVersion = mTargetSdk;
+        return applicationInfo;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
new file mode 100644
index 0000000..328c71d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 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.compat;
+
+import android.content.Context;
+
+import com.android.internal.compat.AndroidBuildClassifier;
+
+import java.util.ArrayList;
+
+/**
+ * Helper class for creating a CompatConfig.
+ */
+class CompatConfigBuilder {
+    private ArrayList<CompatChange> mChanges;
+    private AndroidBuildClassifier mBuildClassifier;
+    private Context mContext;
+
+    private CompatConfigBuilder(AndroidBuildClassifier buildClassifier, Context context) {
+        mChanges = new ArrayList<>();
+        mBuildClassifier = buildClassifier;
+        mContext = context;
+    }
+
+    static CompatConfigBuilder create(AndroidBuildClassifier buildClassifier, Context context) {
+        return new CompatConfigBuilder(buildClassifier, context);
+    }
+
+    CompatConfigBuilder addTargetSdkChangeWithId(int sdk, long id) {
+        mChanges.add(new CompatChange(id, "", sdk, false, ""));
+        return this;
+    }
+
+    CompatConfigBuilder addTargetSdkDisabledChangeWithId(int sdk, long id) {
+        mChanges.add(new CompatChange(id, "", sdk, true, ""));
+        return this;
+    }
+
+    CompatConfigBuilder addTargetSdkChangeWithIdAndName(int sdk, long id, String name) {
+        mChanges.add(new CompatChange(id, name, sdk, false, ""));
+        return this;
+    }
+
+    CompatConfigBuilder addTargetSdkChangeWithIdAndDescription(int sdk, long id,
+            String description) {
+        mChanges.add(new CompatChange(id, "", sdk, false, description));
+        return this;
+    }
+
+    CompatConfigBuilder addEnabledChangeWithId(long id) {
+        mChanges.add(new CompatChange(id, "", -1, false, ""));
+        return this;
+    }
+
+    CompatConfigBuilder addEnabledChangeWithIdAndName(long id, String name) {
+        mChanges.add(new CompatChange(id, name, -1, false, ""));
+        return this;
+    }
+    CompatConfigBuilder addEnabledChangeWithIdAndDescription(long id, String description) {
+        mChanges.add(new CompatChange(id, "", -1, false, description));
+        return this;
+    }
+
+    CompatConfigBuilder addDisabledChangeWithId(long id) {
+        mChanges.add(new CompatChange(id, "", -1, true, ""));
+        return this;
+    }
+
+    CompatConfigBuilder addDisabledChangeWithIdAndName(long id, String name) {
+        mChanges.add(new CompatChange(id, name, -1, true, ""));
+        return this;
+    }
+
+    CompatConfigBuilder addDisabledChangeWithIdAndDescription(long id, String description) {
+        mChanges.add(new CompatChange(id, "", -1, true, description));
+        return this;
+    }
+
+    CompatConfig build() {
+        CompatConfig config = new CompatConfig(mBuildClassifier, mContext);
+        for (CompatChange change : mChanges) {
+            config.addChange(change);
+        }
+        return config;
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index cb99c11..407f67e 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -18,12 +18,25 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.compat.AndroidBuildClassifier;
+
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -34,12 +47,12 @@
 @RunWith(AndroidJUnit4.class)
 public class CompatConfigTest {
 
-    private ApplicationInfo makeAppInfo(String pName, int targetSdkVersion) {
-        ApplicationInfo ai = new ApplicationInfo();
-        ai.packageName = pName;
-        ai.targetSdkVersion = targetSdkVersion;
-        return ai;
-    }
+    @Mock
+    private Context mContext;
+    @Mock
+    PackageManager mPackageManager;
+    @Mock
+    private AndroidBuildClassifier mBuildClassifier;
 
     private File createTempDir() {
         String base = System.getProperty("java.io.tmpdir");
@@ -54,112 +67,206 @@
         os.close();
     }
 
-    @Test
-    public void testUnknownChangeEnabled() {
-        CompatConfig pc = new CompatConfig();
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isTrue();
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        // Assume userdebug/eng non-final build
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(false);
     }
 
     @Test
-    public void testDisabledChangeDisabled() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, ""));
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse();
+    public void testUnknownChangeEnabled() throws Exception {
+        CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create().build()))
+            .isTrue();
     }
 
     @Test
-    public void testTargetSdkChangeDisabled() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false, null));
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse();
+    public void testDisabledChangeDisabled() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L)
+                .build();
+
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create().build()))
+            .isFalse();
     }
 
     @Test
-    public void testTargetSdkChangeEnabled() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false, ""));
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue();
+    public void testTargetSdkChangeDisabled() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addTargetSdkChangeWithId(2, 1234L)
+                .build();
+
+        assertThat(compatConfig.isChangeEnabled(1234L,
+            ApplicationInfoBuilder.create().withTargetSdk(2).build()))
+            .isFalse();
     }
 
     @Test
-    public void testDisabledOverrideTargetSdkChange() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true, null));
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isFalse();
+    public void testTargetSdkChangeEnabled() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addTargetSdkChangeWithId(2, 1234L)
+                .build();
+
+        assertThat(compatConfig.isChangeEnabled(1234L,
+            ApplicationInfoBuilder.create().withTargetSdk(3).build())).isTrue();
     }
 
     @Test
-    public void testGetDisabledChanges() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, null));
-        pc.addChange(new CompatChange(2345L, "OTHER_CHANGE", -1, false, null));
-        assertThat(pc.getDisabledChanges(
-                makeAppInfo("com.some.package", 2))).asList().containsExactly(1234L);
+    public void testDisabledOverrideTargetSdkChange() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addTargetSdkDisabledChangeWithId(2, 1234L)
+                .build();
+
+        assertThat(compatConfig.isChangeEnabled(1234L,
+            ApplicationInfoBuilder.create().withTargetSdk(3).build())).isFalse();
     }
 
     @Test
-    public void testGetDisabledChangesSorted() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true, null));
-        pc.addChange(new CompatChange(123L, "OTHER_CHANGE", 2, true, null));
-        pc.addChange(new CompatChange(12L, "THIRD_CHANGE", 2, true, null));
-        assertThat(pc.getDisabledChanges(
-                makeAppInfo("com.some.package", 2))).asList().containsExactly(12L, 123L, 1234L);
+    public void testGetDisabledChanges() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L)
+                .addEnabledChangeWithId(2345L)
+                .build();
+
+        assertThat(compatConfig.getDisabledChanges(
+            ApplicationInfoBuilder.create().build())).asList().containsExactly(1234L);
     }
 
     @Test
-    public void testPackageOverrideEnabled() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, null)); // disabled
-        pc.addOverride(1234L, "com.some.package", true);
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue();
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isFalse();
+    public void testGetDisabledChangesSorted() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L)
+                .addDisabledChangeWithId(123L)
+                .addDisabledChangeWithId(12L)
+                .build();
+
+        assertThat(compatConfig.getDisabledChanges(ApplicationInfoBuilder.create().build()))
+            .asList().containsExactly(12L, 123L, 1234L);
     }
 
     @Test
-    public void testPackageOverrideDisabled() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null));
-        pc.addOverride(1234L, "com.some.package", false);
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse();
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue();
+    public void testPackageOverrideEnabled() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L)
+                .build();
+
+        compatConfig.addOverride(1234L, "com.some.package", true);
+
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package").build())).isTrue();
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+                .withPackageName("com.other.package").build())).isFalse();
     }
 
     @Test
-    public void testPackageOverrideUnknownPackage() {
-        CompatConfig pc = new CompatConfig();
-        pc.addOverride(1234L, "com.some.package", false);
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse();
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue();
+    public void testPackageOverrideDisabled() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addEnabledChangeWithId(1234L)
+                .build();
+
+        compatConfig.addOverride(1234L, "com.some.package", false);
+
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package").build())).isFalse();
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+                .withPackageName("com.other.package").build())).isTrue();
     }
 
     @Test
-    public void testPackageOverrideUnknownChange() {
-        CompatConfig pc = new CompatConfig();
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isTrue();
+    public void testPackageOverrideUnknownPackage() throws Exception {
+        CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+
+        compatConfig.addOverride(1234L, "com.some.package", false);
+
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package").build())).isFalse();
+        assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+                .withPackageName("com.other.package").build())).isTrue();
     }
 
     @Test
-    public void testRemovePackageOverride() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null));
-        pc.addOverride(1234L, "com.some.package", false);
-        pc.removeOverride(1234L, "com.some.package");
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue();
+    public void testPreventAddOverride() throws Exception {
+        final long changeId = 1234L;
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L)
+                .build();
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package")
+                .build();
+        PackageManager packageManager = mock(PackageManager.class);
+        when(mContext.getPackageManager()).thenReturn(packageManager);
+        when(packageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+            .thenReturn(applicationInfo);
+
+        // Force the validator to prevent overriding the change by using a user build.
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+        assertThrows(SecurityException.class,
+                () -> compatConfig.addOverride(1234L, "com.some.package", true)
+        );
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
     }
 
     @Test
-    public void testLookupChangeId() {
-        CompatConfig pc = new CompatConfig();
-        pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null));
-        pc.addChange(new CompatChange(2345L, "ANOTHER_CHANGE", -1, false, null));
-        assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(1234L);
+    public void testPreventRemoveOverride() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L)
+                .build();
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package")
+                .build();
+        when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+            .thenReturn(applicationInfo);
+        // Assume the override was allowed to be added.
+        compatConfig.addOverride(1234L, "com.some.package", true);
+
+        // Validator allows turning on the change.
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
+
+        // Reject all override attempts.
+        // Force the validator to prevent overriding the change by using a user build.
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+        // Try to turn off change, but validator prevents it.
+        assertThrows(SecurityException.class,
+                () -> compatConfig.removeOverride(1234L, "com.some.package"));
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
     }
 
     @Test
-    public void testLookupChangeIdNotPresent() {
-        CompatConfig pc = new CompatConfig();
-        assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(-1L);
+    public void testRemovePackageOverride() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addEnabledChangeWithId(1234L)
+                .build();
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.some.package")
+                .build();
+
+        assertThat(compatConfig.addOverride(1234L, "com.some.package", false)).isTrue();
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
+
+        compatConfig.removeOverride(1234L, "com.some.package");
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
+    }
+
+    @Test
+    public void testLookupChangeId() throws Exception {
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addEnabledChangeWithIdAndName(1234L, "MY_CHANGE")
+                .addEnabledChangeWithIdAndName(2345L, "MY_OTHER_CHANGE")
+                .build();
+
+        assertThat(compatConfig.lookupChangeId("MY_CHANGE")).isEqualTo(1234L);
+    }
+
+    @Test
+    public void testLookupChangeIdNotPresent() throws Exception {
+        CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+        assertThat(compatConfig.lookupChangeId("MY_CHANGE")).isEqualTo(-1L);
     }
 
     @Test
@@ -172,14 +279,17 @@
 
         File dir = createTempDir();
         writeToFile(dir, "platform_compat_config.xml", configXml);
+        CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+        compatConfig.initConfigFromLib(dir);
 
-        CompatConfig pc = new CompatConfig();
-        pc.initConfigFromLib(dir);
-
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse();
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue();
-        assertThat(pc.isChangeEnabled(1235L, makeAppInfo("com.some.package", 5))).isFalse();
-        assertThat(pc.isChangeEnabled(1236L, makeAppInfo("com.some.package", 1))).isTrue();
+        assertThat(compatConfig.isChangeEnabled(1234L,
+            ApplicationInfoBuilder.create().withTargetSdk(1).build())).isFalse();
+        assertThat(compatConfig.isChangeEnabled(1234L,
+            ApplicationInfoBuilder.create().withTargetSdk(3).build())).isTrue();
+        assertThat(compatConfig.isChangeEnabled(1235L,
+            ApplicationInfoBuilder.create().withTargetSdk(5).build())).isFalse();
+        assertThat(compatConfig.isChangeEnabled(1236L,
+            ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue();
     }
 
     @Test
@@ -195,15 +305,16 @@
         File dir = createTempDir();
         writeToFile(dir, "libcore_platform_compat_config.xml", configXml1);
         writeToFile(dir, "frameworks_platform_compat_config.xml", configXml2);
+        CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+        compatConfig.initConfigFromLib(dir);
 
-        CompatConfig pc = new CompatConfig();
-        pc.initConfigFromLib(dir);
-
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse();
-        assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue();
-        assertThat(pc.isChangeEnabled(1235L, makeAppInfo("com.some.package", 5))).isFalse();
-        assertThat(pc.isChangeEnabled(1236L, makeAppInfo("com.some.package", 1))).isTrue();
+        assertThat(compatConfig.isChangeEnabled(1234L,
+            ApplicationInfoBuilder.create().withTargetSdk(1).build())).isFalse();
+        assertThat(compatConfig.isChangeEnabled(1234L,
+            ApplicationInfoBuilder.create().withTargetSdk(3).build())).isTrue();
+        assertThat(compatConfig.isChangeEnabled(1235L,
+            ApplicationInfoBuilder.create().withTargetSdk(5).build())).isFalse();
+        assertThat(compatConfig.isChangeEnabled(1236L,
+            ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue();
     }
 }
-
-
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatibilityChangeConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatibilityChangeConfigBuilder.java
new file mode 100644
index 0000000..793296e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatibilityChangeConfigBuilder.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 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.compat;
+
+import android.compat.Compatibility;
+
+import com.android.internal.compat.CompatibilityChangeConfig;
+
+import java.util.HashSet;
+import java.util.Set;
+
+class CompatibilityChangeConfigBuilder {
+    private Set<Long> mEnabled;
+    private Set<Long> mDisabled;
+
+    private CompatibilityChangeConfigBuilder() {
+        mEnabled = new HashSet<>();
+        mDisabled = new HashSet<>();
+    }
+
+    static CompatibilityChangeConfigBuilder create() {
+        return new CompatibilityChangeConfigBuilder();
+    }
+
+    CompatibilityChangeConfigBuilder enable(Long id) {
+        mEnabled.add(id);
+        return this;
+    }
+
+    CompatibilityChangeConfigBuilder disable(Long id) {
+        mDisabled.add(id);
+        return this;
+    }
+
+    CompatibilityChangeConfig build() {
+        return new CompatibilityChangeConfig(new Compatibility.ChangeConfig(mEnabled, mDisabled));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
new file mode 100644
index 0000000..ecd07bd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2019 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.compat;
+
+import static com.android.internal.compat.OverrideAllowedState.ALLOWED;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.compat.AndroidBuildClassifier;
+import com.android.internal.compat.IOverrideValidator;
+import com.android.internal.compat.OverrideAllowedState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class OverrideValidatorImplTest {
+    private static final String PACKAGE_NAME = "my.package";
+    private static final int TARGET_SDK = 10;
+    private static final int TARGET_SDK_BEFORE = 9;
+    private static final int TARGET_SDK_AFTER = 11;
+
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    Context mContext;
+
+    private AndroidBuildClassifier debuggableBuild() {
+        AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class);
+        when(buildClassifier.isDebuggableBuild()).thenReturn(true);
+        return buildClassifier;
+    }
+
+    private AndroidBuildClassifier betaBuild() {
+        AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class);
+        when(buildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(buildClassifier.isFinalBuild()).thenReturn(false);
+        return buildClassifier;
+    }
+
+    private AndroidBuildClassifier finalBuild() {
+        AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class);
+        when(buildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(buildClassifier.isFinalBuild()).thenReturn(true);
+        return buildClassifier;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+    }
+
+    @Test
+    public void getOverrideAllowedState_debugBuildAnyChangeDebugApp_allowOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext)
+                    .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                    .addTargetSdkChangeWithId(TARGET_SDK, 2)
+                    .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3)
+                    .addEnabledChangeWithId(4)
+                    .addDisabledChangeWithId(5).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .debuggable()
+                        .withTargetSdk(TARGET_SDK)
+                        .withPackageName(PACKAGE_NAME).build());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkAfterChange =
+                overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+        OverrideAllowedState stateEnabledChange =
+                overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+        OverrideAllowedState stateDisabledChange =
+                overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateTargetSdkEqualChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateTargetSdkAfterChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateEnabledChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateDisabledChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_debugBuildAnyChangeReleaseApp_allowOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext)
+                    .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                    .addTargetSdkChangeWithId(TARGET_SDK, 2)
+                    .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3)
+                    .addEnabledChangeWithId(4)
+                    .addDisabledChangeWithId(5).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .withTargetSdk(TARGET_SDK).build());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkAfterChange =
+                overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+        OverrideAllowedState stateEnabledChange =
+                overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+        OverrideAllowedState stateDisabledChange =
+                overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateTargetSdkEqualChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateTargetSdkAfterChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateEnabledChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+        assertThat(stateDisabledChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_betaBuildTargetSdkChangeDebugApp_allowOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
+                        .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                        .addTargetSdkChangeWithId(TARGET_SDK, 2)
+                        .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .debuggable()
+                        .withTargetSdk(TARGET_SDK)
+                        .withPackageName(PACKAGE_NAME).build());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkAfterChange =
+                overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_BEFORE));
+        assertThat(stateTargetSdkEqualChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK));
+        assertThat(stateTargetSdkAfterChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_AFTER));
+    }
+
+    @Test
+    public void getOverrideAllowedState_betaBuildEnabledChangeDebugApp_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
+                        .addEnabledChangeWithId(1).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .debuggable()
+                        .build());
+
+        OverrideAllowedState allowedState =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+        assertThat(allowedState)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_betaBuildDisabledChangeDebugApp_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
+                        .addDisabledChangeWithId(1).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .debuggable()
+                        .withPackageName(PACKAGE_NAME).build());
+
+        OverrideAllowedState allowedState =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+        assertThat(allowedState)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_betaBuildAnyChangeReleaseApp_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
+                        .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                        .addTargetSdkChangeWithId(TARGET_SDK, 2)
+                        .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3)
+                        .addEnabledChangeWithId(4)
+                        .addDisabledChangeWithId(5).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .withTargetSdk(TARGET_SDK).build());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkAfterChange =
+                overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+        OverrideAllowedState stateEnabledChange =
+                overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+        OverrideAllowedState stateDisabledChange =
+                overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateTargetSdkEqualChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateTargetSdkAfterChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateEnabledChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateDisabledChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_finalBuildTargetSdkChangeDebugAppOptin_allowOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+                        .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 1).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .debuggable()
+                        .withTargetSdk(TARGET_SDK)
+                        .withPackageName(PACKAGE_NAME).build());
+
+        OverrideAllowedState allowedState =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+        assertThat(allowedState)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_AFTER));
+    }
+
+    @Test
+    public void getOverrideAllowedState_finalBuildTargetSdkChangeDebugAppOptout_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+                        .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                        .addTargetSdkChangeWithId(TARGET_SDK, 2).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .withTargetSdk(TARGET_SDK)
+                        .debuggable()
+                        .build());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange).isEqualTo(
+                new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, TARGET_SDK,
+                                         TARGET_SDK_BEFORE));
+        assertThat(stateTargetSdkEqualChange).isEqualTo(
+                new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, TARGET_SDK, TARGET_SDK));
+    }
+
+    @Test
+    public void getOverrideAllowedState_finalBuildEnabledChangeDebugApp_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+                        .addEnabledChangeWithId(1).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .debuggable().build());
+
+        OverrideAllowedState allowedState =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+        assertThat(allowedState)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_finalBuildDisabledChangeDebugApp_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+                        .addDisabledChangeWithId(1).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .debuggable().build());
+
+        OverrideAllowedState allowedState =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+        assertThat(allowedState)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_finalBuildAnyChangeReleaseApp_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+                        .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                        .addTargetSdkChangeWithId(TARGET_SDK, 2)
+                        .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3)
+                        .addEnabledChangeWithId(4)
+                        .addDisabledChangeWithId(5).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .withTargetSdk(TARGET_SDK).build());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkAfterChange =
+                overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+        OverrideAllowedState stateEnabledChange =
+                overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+        OverrideAllowedState stateDisabledChange =
+                overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateTargetSdkEqualChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateTargetSdkAfterChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateEnabledChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateDisabledChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index c406876..ce5d6d9 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -26,21 +26,20 @@
 import static org.mockito.internal.verification.VerificationModeFactory.times;
 import static org.testng.Assert.assertThrows;
 
-import android.compat.Compatibility;
 import android.content.Context;
 import android.content.pm.PackageManager;
 
-import com.android.internal.compat.CompatibilityChangeConfig;
+import androidx.test.runner.AndroidJUnit4;
 
-import com.google.common.collect.ImmutableSet;
+import com.android.internal.compat.AndroidBuildClassifier;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.MockitoAnnotations;
 
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(AndroidJUnit4.class)
 public class PlatformCompatTest {
     private static final String PACKAGE_NAME = "my.package";
 
@@ -50,84 +49,77 @@
     private PackageManager mPackageManager;
     @Mock
     CompatChange.ChangeListener mListener1, mListener2;
-
+    PlatformCompat mPlatformCompat;
+    CompatConfig mCompatConfig;
+    @Mock
+    private AndroidBuildClassifier mBuildClassifier;
 
     @Before
     public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow(
                 new PackageManager.NameNotFoundException());
-        CompatConfig.get().clearChanges();
+        mCompatConfig = new CompatConfig(mBuildClassifier, mContext);
+        mPlatformCompat = new PlatformCompat(mContext, mCompatConfig);
+        // Assume userdebug/eng non-final build
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(false);
     }
 
     @Test
-    public void testRegisterListenerToSameIdThrows() {
-        PlatformCompat pc = new PlatformCompat(mContext);
-
+    public void testRegisterListenerToSameIdThrows() throws Exception {
         // Registering a listener to change 1 is successful.
-        pc.registerListener(1, mListener1);
+        mPlatformCompat.registerListener(1, mListener1);
         // Registering a listener to change 2 is successful.
-        pc.registerListener(2, mListener1);
+        mPlatformCompat.registerListener(2, mListener1);
         // Trying to register another listener to change id 1 fails.
-        assertThrows(IllegalStateException.class, () -> pc.registerListener(1, mListener1));
+        assertThrows(IllegalStateException.class,
+                () -> mPlatformCompat.registerListener(1, mListener1));
     }
 
     @Test
-    public void testRegisterListenerReturn() {
-        PlatformCompat pc = new PlatformCompat(mContext);
-
-        pc.setOverrides(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())),
+    public void testRegisterListenerReturn() throws Exception {
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).build(),
                 PACKAGE_NAME);
 
         // Change id 1 is known (added in setOverrides).
-        assertThat(pc.registerListener(1, mListener1)).isTrue();
+        assertThat(mPlatformCompat.registerListener(1, mListener1)).isTrue();
         // Change 2 is unknown.
-        assertThat(pc.registerListener(2, mListener1)).isFalse();
+        assertThat(mPlatformCompat.registerListener(2, mListener1)).isFalse();
     }
 
     @Test
-    public void testListenerCalledOnSetOverrides() {
-        PlatformCompat pc = new PlatformCompat(mContext);
+    public void testListenerCalledOnSetOverrides() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
+        mPlatformCompat.registerListener(2, mListener1);
 
-        pc.registerListener(1, mListener1);
-        pc.registerListener(2, mListener1);
-
-        pc.setOverrides(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
 
         verify(mListener1, times(2)).onCompatChange(PACKAGE_NAME);
     }
 
     @Test
-    public void testListenerNotCalledOnWrongPackage() {
-        PlatformCompat pc = new PlatformCompat(mContext);
+    public void testListenerNotCalledOnWrongPackage() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
+        mPlatformCompat.registerListener(2, mListener1);
 
-        pc.registerListener(1, mListener1);
-        pc.registerListener(2, mListener1);
-
-        pc.setOverridesForTest(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
 
         verify(mListener1, never()).onCompatChange("other.package");
     }
 
     @Test
-    public void testListenerCalledOnSetOverridesTwoListeners() {
-        PlatformCompat pc = new PlatformCompat(mContext);
-        pc.registerListener(1, mListener1);
+    public void testListenerCalledOnSetOverridesTwoListeners() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
 
-        final ImmutableSet<Long> enabled = ImmutableSet.of(1L);
-        final ImmutableSet<Long> disabled = ImmutableSet.of(2L);
-
-        pc.setOverrides(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(enabled, disabled)),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
 
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
@@ -136,11 +128,10 @@
         reset(mListener1);
         reset(mListener2);
 
-        pc.registerListener(2, mListener2);
+        mPlatformCompat.registerListener(2, mListener2);
 
-        pc.setOverrides(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(enabled, disabled)),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
 
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
@@ -148,31 +139,23 @@
     }
 
     @Test
-    public void testListenerCalledOnSetOverridesForTest() {
-        PlatformCompat pc = new PlatformCompat(mContext);
+    public void testListenerCalledOnSetOverridesForTest() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
+        mPlatformCompat.registerListener(2, mListener1);
 
-        pc.registerListener(1, mListener1);
-        pc.registerListener(2, mListener1);
-
-        pc.setOverridesForTest(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
 
         verify(mListener1, times(2)).onCompatChange(PACKAGE_NAME);
     }
 
     @Test
-    public void testListenerCalledOnSetOverridesTwoListenersForTest() {
-        PlatformCompat pc = new PlatformCompat(mContext);
-        pc.registerListener(1, mListener1);
+    public void testListenerCalledOnSetOverridesTwoListenersForTest() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
 
-        final ImmutableSet<Long> enabled = ImmutableSet.of(1L);
-        final ImmutableSet<Long> disabled = ImmutableSet.of(2L);
-
-        pc.setOverridesForTest(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(enabled, disabled)),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
 
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
@@ -181,10 +164,10 @@
         reset(mListener1);
         reset(mListener2);
 
-        pc.registerListener(2, mListener2);
-        pc.setOverridesForTest(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(enabled, disabled)),
+        mPlatformCompat.registerListener(2, mListener2);
+
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
 
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
@@ -192,15 +175,12 @@
     }
 
     @Test
-    public void testListenerCalledOnClearOverrides() {
-        PlatformCompat pc = new PlatformCompat(mContext);
+    public void testListenerCalledOnClearOverrides() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
+        mPlatformCompat.registerListener(2, mListener2);
 
-        pc.registerListener(1, mListener1);
-        pc.registerListener(2, mListener2);
-
-        pc.setOverrides(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).build(),
                 PACKAGE_NAME);
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
         verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
@@ -208,21 +188,18 @@
         reset(mListener1);
         reset(mListener2);
 
-        pc.clearOverrides(PACKAGE_NAME);
+        mPlatformCompat.clearOverrides(PACKAGE_NAME);
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
         verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
     }
 
     @Test
-    public void testListenerCalledOnClearOverridesMultipleOverrides() {
-        PlatformCompat pc = new PlatformCompat(mContext);
+    public void testListenerCalledOnClearOverridesMultipleOverrides() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
+        mPlatformCompat.registerListener(2, mListener2);
 
-        pc.registerListener(1, mListener1);
-        pc.registerListener(2, mListener2);
-
-        pc.setOverrides(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
                 PACKAGE_NAME);
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
         verify(mListener2, times(1)).onCompatChange(PACKAGE_NAME);
@@ -230,21 +207,18 @@
         reset(mListener1);
         reset(mListener2);
 
-        pc.clearOverrides(PACKAGE_NAME);
+        mPlatformCompat.clearOverrides(PACKAGE_NAME);
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
         verify(mListener2, times(1)).onCompatChange(PACKAGE_NAME);
     }
 
     @Test
-    public void testListenerCalledOnClearOverrideExists() {
-        PlatformCompat pc = new PlatformCompat(mContext);
+    public void testListenerCalledOnClearOverrideExists() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
+        mPlatformCompat.registerListener(2, mListener2);
 
-        pc.registerListener(1, mListener1);
-        pc.registerListener(2, mListener2);
-
-        pc.setOverrides(
-                new CompatibilityChangeConfig(
-                        new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())),
+        mPlatformCompat.setOverrides(
+                CompatibilityChangeConfigBuilder.create().enable(1L).build(),
                 PACKAGE_NAME);
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
         verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
@@ -252,21 +226,17 @@
         reset(mListener1);
         reset(mListener2);
 
-        pc.clearOverride(1, PACKAGE_NAME);
+        mPlatformCompat.clearOverride(1, PACKAGE_NAME);
         verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
         verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
     }
 
     @Test
-    public void testListenerCalledOnClearOverrideDoesntExist() {
-        PlatformCompat pc = new PlatformCompat(mContext);
+    public void testListenerCalledOnClearOverrideDoesntExist() throws Exception {
+        mPlatformCompat.registerListener(1, mListener1);
 
-        pc.registerListener(1, mListener1);
-
-        pc.clearOverride(1, PACKAGE_NAME);
+        mPlatformCompat.clearOverride(1, PACKAGE_NAME);
         // Listener not called when a non existing override is removed.
         verify(mListener1, never()).onCompatChange(PACKAGE_NAME);
     }
-
-
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ee94dd6..175c756 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3636,6 +3636,29 @@
         verify(getServices().settings).settingsGlobalPutInt(Settings.Global.AUTO_TIME_ZONE, 0);
     }
 
+    public void testIsOrganizationOwnedDevice() throws Exception {
+        setupProfileOwner();
+        // Set up the user manager to return correct user info
+        UserInfo managedProfileUserInfo = new UserInfo(DpmMockContext.CALLER_USER_HANDLE,
+                "managed profile",
+                UserInfo.FLAG_MANAGED_PROFILE);
+        when(getServices().userManager.getUsers())
+                .thenReturn(Arrays.asList(managedProfileUserInfo));
+
+        // Any caller without the MANAGE_USERS permission should get a security exception.
+        assertExpectException(SecurityException.class, null, () ->
+                dpm.isOrganizationOwnedDeviceWithManagedProfile());
+        // But when the right permission is granted, this should succeed.
+        mContext.permissions.add(android.Manifest.permission.MANAGE_USERS);
+        assertFalse(dpm.isOrganizationOwnedDeviceWithManagedProfile());
+        configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+        assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile());
+
+        // A random caller from another user should also be able to get the right result.
+        mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
+        assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile());
+    }
+
     public void testSetTime() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
@@ -5525,6 +5548,36 @@
         assertTrue(dpm.isPackageAllowedToAccessCalendar(testPackage));
     }
 
+    public void testSetProtectedPackages_asDO() throws Exception {
+        final List<String> testPackages = new ArrayList<>();
+        testPackages.add("package_1");
+        testPackages.add("package_2");
+
+        mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+        setDeviceOwner();
+
+        dpm.setProtectedPackages(admin1, testPackages);
+
+        verify(getServices().packageManagerInternal).setDeviceOwnerProtectedPackages(testPackages);
+
+        assertEquals(testPackages, dpm.getProtectedPackages(admin1));
+    }
+
+    public void testSetProtectedPackages_failingAsPO() throws Exception {
+        final List<String> testPackages = new ArrayList<>();
+        testPackages.add("package_1");
+        testPackages.add("package_2");
+
+        mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+        setAsProfileOwner(admin1);
+
+        assertExpectException(SecurityException.class, /* messageRegex= */ null,
+                () -> dpm.setProtectedPackages(admin1, testPackages));
+
+        assertExpectException(SecurityException.class, /* messageRegex= */ null,
+                () -> dpm.getProtectedPackages(admin1));
+    }
+
     private void configureProfileOwnerOfOrgOwnedDevice(ComponentName who, int userId) {
         when(getServices().userManager.getProfileParent(eq(UserHandle.of(userId))))
                 .thenReturn(UserHandle.SYSTEM);
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
new file mode 100644
index 0000000..742952e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2020 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.integrity.parser;
+
+import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
+import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
+import static com.android.server.integrity.utils.TestUtils.getBits;
+import static com.android.server.integrity.utils.TestUtils.getBytes;
+import static com.android.server.integrity.utils.TestUtils.getValueBits;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.integrity.AppInstallMetadata;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class RuleIndexingControllerTest {
+
+    @Test
+    public void verifyIndexRangeSearchIsCorrect() throws IOException {
+        InputStream inputStream = obtainDefaultIndexingMapForTest();
+
+        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
+
+        AppInstallMetadata appInstallMetadata =
+                new AppInstallMetadata.Builder()
+                        .setPackageName("ddd")
+                        .setAppCertificate("777")
+                        .build();
+
+        List<RuleIndexRange> resultingIndexes =
+                indexingController.identifyRulesToEvaluate(appInstallMetadata);
+
+        assertThat(resultingIndexes)
+                .containsExactly(
+                        new RuleIndexRange(200, 300),
+                        new RuleIndexRange(700, 800),
+                        new RuleIndexRange(900, 945));
+    }
+
+    @Test
+    public void verifyIndexRangeSearchIsCorrect_keysInFirstAndLastBlock() throws IOException {
+        InputStream inputStream = obtainDefaultIndexingMapForTest();
+
+        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
+
+        AppInstallMetadata appInstallMetadata =
+                new AppInstallMetadata.Builder()
+                        .setPackageName("bbb")
+                        .setAppCertificate("999")
+                        .build();
+
+        List<RuleIndexRange> resultingIndexes =
+                indexingController.identifyRulesToEvaluate(appInstallMetadata);
+
+        assertThat(resultingIndexes)
+                .containsExactly(
+                        new RuleIndexRange(100, 200),
+                        new RuleIndexRange(800, 900),
+                        new RuleIndexRange(900, 945));
+    }
+
+    @Test
+    public void verifyIndexRangeSearchIsCorrect_keysMatchWithValues() throws IOException {
+        InputStream inputStream = obtainDefaultIndexingMapForTest();
+
+        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
+
+        AppInstallMetadata appInstallMetadata =
+                new AppInstallMetadata.Builder()
+                        .setPackageName("ccc")
+                        .setAppCertificate("444")
+                        .build();
+
+        List<RuleIndexRange> resultingIndexes =
+                indexingController.identifyRulesToEvaluate(appInstallMetadata);
+
+        assertThat(resultingIndexes)
+                .containsExactly(
+                        new RuleIndexRange(200, 300),
+                        new RuleIndexRange(700, 800),
+                        new RuleIndexRange(900, 945));
+    }
+
+    @Test
+    public void verifyIndexRangeSearchIsCorrect_noIndexesAvailable() throws IOException {
+        byte[] stringBytes =
+                getBytes(
+                        getKeyValueString(START_INDEXING_KEY, 100)
+                                + getKeyValueString(END_INDEXING_KEY, 500)
+                                + getKeyValueString(START_INDEXING_KEY, 500)
+                                + getKeyValueString(END_INDEXING_KEY, 900)
+                                + getKeyValueString(START_INDEXING_KEY, 900)
+                                + getKeyValueString(END_INDEXING_KEY, 945));
+        ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
+        rule.put(stringBytes);
+        InputStream inputStream = new ByteArrayInputStream(rule.array());
+
+        RuleIndexingController indexingController = new RuleIndexingController(inputStream);
+
+        AppInstallMetadata appInstallMetadata =
+                new AppInstallMetadata.Builder()
+                        .setPackageName("ccc")
+                        .setAppCertificate("444")
+                        .build();
+
+        List<RuleIndexRange> resultingIndexes =
+                indexingController.identifyRulesToEvaluate(appInstallMetadata);
+
+        assertThat(resultingIndexes)
+                .containsExactly(
+                        new RuleIndexRange(100, 500),
+                        new RuleIndexRange(500, 900),
+                        new RuleIndexRange(900, 945));
+    }
+
+    private static InputStream obtainDefaultIndexingMapForTest() {
+        byte[] stringBytes =
+                getBytes(
+                        getKeyValueString(START_INDEXING_KEY, 100)
+                                + getKeyValueString("ccc", 200)
+                                + getKeyValueString("eee", 300)
+                                + getKeyValueString("hhh", 400)
+                                + getKeyValueString(END_INDEXING_KEY, 500)
+                                + getKeyValueString(START_INDEXING_KEY, 500)
+                                + getKeyValueString("111", 600)
+                                + getKeyValueString("444", 700)
+                                + getKeyValueString("888", 800)
+                                + getKeyValueString(END_INDEXING_KEY, 900)
+                                + getKeyValueString(START_INDEXING_KEY, 900)
+                                + getKeyValueString(END_INDEXING_KEY, 945));
+        ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
+        rule.put(stringBytes);
+        return new ByteArrayInputStream(rule.array());
+    }
+
+    private static String getKeyValueString(String key, int value) {
+        String isNotHashed = "0";
+        return isNotHashed
+                + getBits(key.length(), VALUE_SIZE_BITS)
+                + getValueBits(key)
+                + getBits(value, /* numOfBits= */ 32);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java b/services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java
similarity index 61%
copy from packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
copy to services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java
index 155a6d2..e159012 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
+++ b/services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java
@@ -14,17 +14,23 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dagger.qualifiers;
+package com.android.server.location;
 
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import android.content.Context;
+import android.location.Country;
 
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
+public class CustomCountryDetectorTestClass extends CountryDetectorBase {
+    public CustomCountryDetectorTestClass(Context ctx) {
+        super(ctx);
+    }
 
-import javax.inject.Qualifier;
+    @Override
+    public Country detectCountry() {
+        return null;
+    }
 
-@Qualifier
-@Documented
-@Retention(RUNTIME)
-public @interface DisplayId {
+    @Override
+    public void stop() {
+
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
new file mode 100644
index 0000000..54c552b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 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.locksettings;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * atest FrameworksServicesTests:RebootEscrowDataTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class RebootEscrowDataTest {
+    private static byte[] getTestSp() {
+        byte[] testSp = new byte[10];
+        for (int i = 0; i < testSp.length; i++) {
+            testSp[i] = (byte) i;
+        }
+        return testSp;
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void fromEntries_failsOnNull() throws Exception {
+        RebootEscrowData.fromSyntheticPassword((byte) 2, null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void fromEncryptedData_failsOnNullData() throws Exception {
+        byte[] testSp = getTestSp();
+        RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword((byte) 2, testSp);
+        SecretKeySpec key = RebootEscrowData.fromKeyBytes(expected.getKey());
+        RebootEscrowData.fromEncryptedData(key, null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void fromEncryptedData_failsOnNullKey() throws Exception {
+        byte[] testSp = getTestSp();
+        RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword((byte) 2, testSp);
+        RebootEscrowData.fromEncryptedData(null, expected.getBlob());
+    }
+
+    @Test
+    public void fromEntries_loopback_success() throws Exception {
+        byte[] testSp = getTestSp();
+        RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword((byte) 2, testSp);
+
+        SecretKeySpec key = RebootEscrowData.fromKeyBytes(expected.getKey());
+        RebootEscrowData actual = RebootEscrowData.fromEncryptedData(key, expected.getBlob());
+
+        assertThat(actual.getSpVersion(), is(expected.getSpVersion()));
+        assertThat(actual.getIv(), is(expected.getIv()));
+        assertThat(actual.getKey(), is(expected.getKey()));
+        assertThat(actual.getBlob(), is(expected.getBlob()));
+        assertThat(actual.getSyntheticPassword(), is(expected.getSyntheticPassword()));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
new file mode 100644
index 0000000..78a5a0b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2020 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.locksettings;
+
+import static android.content.pm.UserInfo.FLAG_FULL;
+import static android.content.pm.UserInfo.FLAG_PRIMARY;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.rebootescrow.IRebootEscrow;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.RebootEscrowListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.ArrayList;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RebootEscrowManagerTests {
+    protected static final int PRIMARY_USER_ID = 0;
+    protected static final int NONSECURE_USER_ID = 10;
+    private static final byte FAKE_SP_VERSION = 1;
+    private static final byte[] FAKE_AUTH_TOKEN = new byte[] {
+            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+    };
+
+    private Context mContext;
+    private UserManager mUserManager;
+    private RebootEscrowManager.Callbacks mCallbacks;
+    private IRebootEscrow mRebootEscrow;
+
+    LockSettingsStorageTestable mStorage;
+
+    private RebootEscrowManager mService;
+
+    static class MockInjector extends RebootEscrowManager.Injector {
+        private final IRebootEscrow mRebootEscrow;
+        private final UserManager mUserManager;
+
+        MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow) {
+            super(context);
+            mRebootEscrow = rebootEscrow;
+            mUserManager = userManager;
+        }
+
+        @Override
+        public UserManager getUserManager() {
+            return mUserManager;
+        }
+
+        @Override
+        public IRebootEscrow getRebootEscrow() {
+            return mRebootEscrow;
+        }
+    }
+
+    @Before
+    public void setUp_baseServices() throws Exception {
+        mContext = mock(Context.class);
+        mUserManager = mock(UserManager.class);
+        mCallbacks = mock(RebootEscrowManager.Callbacks.class);
+        mRebootEscrow = mock(IRebootEscrow.class);
+
+        mStorage = new LockSettingsStorageTestable(mContext,
+                new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
+
+        ArrayList<UserInfo> users = new ArrayList<>();
+        users.add(new UserInfo(PRIMARY_USER_ID, "primary", FLAG_PRIMARY));
+        users.add(new UserInfo(NONSECURE_USER_ID, "non-secure", FLAG_FULL));
+        when(mUserManager.getUsers()).thenReturn(users);
+        when(mCallbacks.isUserSecure(PRIMARY_USER_ID)).thenReturn(true);
+        when(mCallbacks.isUserSecure(NONSECURE_USER_ID)).thenReturn(false);
+        mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow),
+                mCallbacks, mStorage);
+    }
+
+    @Test
+    public void prepareRebootEscrow_Success() throws Exception {
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mRebootEscrow);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mRebootEscrow, never()).storeKey(any());
+    }
+
+    @Test
+    public void prepareRebootEscrow_ClearCredentials_Success() throws Exception {
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+
+        clearInvocations(mRebootEscrow);
+        mService.clearRebootEscrow();
+        verify(mockListener).onPreparedForReboot(eq(false));
+        verify(mRebootEscrow).storeKey(eq(new byte[32]));
+    }
+
+    @Test
+    public void armService_Success() throws Exception {
+        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+        mService.setRebootEscrowListener(mockListener);
+        mService.prepareRebootEscrow();
+
+        clearInvocations(mRebootEscrow);
+        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+        verify(mockListener).onPreparedForReboot(eq(true));
+        verify(mRebootEscrow, never()).storeKey(any());
+
+        assertTrue(mService.armRebootEscrowIfNeeded());
+        verify(mRebootEscrow).storeKey(any());
+    }
+
+    @Test
+    public void armService_NoInitialization_Failure() throws Exception {
+        assertFalse(mService.armRebootEscrowIfNeeded());
+        verifyNoMoreInteractions(mRebootEscrow);
+    }
+
+    @Test
+    public void armService_RebootEscrowServiceException_Failure() throws Exception {
+        doThrow(RemoteException.class).when(mRebootEscrow).storeKey(any());
+        assertFalse(mService.armRebootEscrowIfNeeded());
+        verifyNoMoreInteractions(mRebootEscrow);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
new file mode 100644
index 0000000..d3166b9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 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.people;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTarget;
+import android.app.prediction.IPredictionCallback;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.RemoteException;
+
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+@RunWith(JUnit4.class)
+public final class PeopleServiceTest {
+
+    private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
+    private static final int APP_PREDICTION_TARGET_COUNT = 4;
+    private static final String TEST_PACKAGE_NAME = "com.example";
+
+    private PeopleServiceInternal mServiceInternal;
+    private PeopleService.LocalService mLocalService;
+    private AppPredictionSessionId mSessionId;
+    private AppPredictionContext mPredictionContext;
+
+    @Mock private Context mContext;
+    @Mock private IPredictionCallback mCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(mCallback.asBinder()).thenReturn(new Binder());
+
+        PeopleService service = new PeopleService(mContext);
+        service.onStart();
+
+        mServiceInternal = LocalServices.getService(PeopleServiceInternal.class);
+        mLocalService = (PeopleService.LocalService) mServiceInternal;
+
+        mSessionId = new AppPredictionSessionId("abc");
+        mPredictionContext = new AppPredictionContext.Builder(mContext)
+                .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
+                .setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT)
+                .build();
+    }
+
+    @After
+    public void tearDown() {
+        LocalServices.removeServiceForTest(PeopleServiceInternal.class);
+    }
+
+    @Test
+    public void testRegisterCallbacks() throws RemoteException {
+        mServiceInternal.onCreatePredictionSession(mPredictionContext, mSessionId);
+
+        SessionInfo sessionInfo = mLocalService.getSessionInfo(mSessionId);
+
+        mServiceInternal.registerPredictionUpdates(mSessionId, mCallback);
+
+        Consumer<List<AppTarget>> updatePredictionMethod =
+                sessionInfo.getPredictor().getUpdatePredictionsMethod();
+        updatePredictionMethod.accept(new ArrayList<>());
+        updatePredictionMethod.accept(new ArrayList<>());
+
+        verify(mCallback, times(2)).onResult(any(ParceledListSlice.class));
+
+        mServiceInternal.unregisterPredictionUpdates(mSessionId, mCallback);
+
+        updatePredictionMethod.accept(new ArrayList<>());
+
+        // After the un-registration, the callback should no longer be called.
+        verify(mCallback, times(2)).onResult(any(ParceledListSlice.class));
+
+        mServiceInternal.onDestroyPredictionSession(mSessionId);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 1e760cc..ac27a08 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -139,18 +139,18 @@
         assertEquals("A Name", mUserManagerService.getUserInfo(TEST_ID).name);
     }
 
-    /** Test UMS.getUserTypeForUser(). */
+    /** Test UMS.isUserOfType(). */
     @Test
-    public void testGetUserTypeForUser() throws Exception {
-        final String typeSys = mUserManagerService.getUserTypeForUser(UserHandle.USER_SYSTEM);
-        assertTrue("System user was of invalid type " + typeSys,
-                typeSys.equals(USER_TYPE_SYSTEM_HEADLESS) || typeSys.equals(USER_TYPE_FULL_SYSTEM));
+    public void testIsUserOfType() throws Exception {
+        assertTrue("System user was of invalid type",
+                mUserManagerService.isUserOfType(UserHandle.USER_SYSTEM, USER_TYPE_SYSTEM_HEADLESS)
+                || mUserManagerService.isUserOfType(UserHandle.USER_SYSTEM, USER_TYPE_FULL_SYSTEM));
 
         final int testId = 100;
         final String typeName = "A type";
         UserInfo userInfo = createUser(testId, 0, typeName);
         mUserManagerService.putUserInfo(userInfo);
-        assertEquals(typeName, mUserManagerService.getUserTypeForUser(testId));
+        assertTrue(mUserManagerService.isUserOfType(testId, typeName));
     }
 
     /** Tests upgradeIfNecessaryLP (but without locking) for upgrading from version 8 to 9+. */
@@ -169,22 +169,22 @@
 
         mUserManagerService.upgradeIfNecessaryLP(null, versionToTest - 1);
 
-        assertEquals(USER_TYPE_PROFILE_MANAGED, mUserManagerService.getUserTypeForUser(100));
+        assertTrue(mUserManagerService.isUserOfType(100, USER_TYPE_PROFILE_MANAGED));
         assertTrue((mUserManagerService.getUserInfo(100).flags & FLAG_PROFILE) != 0);
 
-        assertEquals(USER_TYPE_FULL_GUEST, mUserManagerService.getUserTypeForUser(101));
+        assertTrue(mUserManagerService.isUserOfType(101, USER_TYPE_FULL_GUEST));
 
-        assertEquals(USER_TYPE_FULL_RESTRICTED, mUserManagerService.getUserTypeForUser(102));
+        assertTrue(mUserManagerService.isUserOfType(102, USER_TYPE_FULL_RESTRICTED));
         assertTrue((mUserManagerService.getUserInfo(102).flags & FLAG_PROFILE) == 0);
 
-        assertEquals(USER_TYPE_FULL_SECONDARY, mUserManagerService.getUserTypeForUser(103));
+        assertTrue(mUserManagerService.isUserOfType(103, USER_TYPE_FULL_SECONDARY));
         assertTrue((mUserManagerService.getUserInfo(103).flags & FLAG_PROFILE) == 0);
 
-        assertEquals(USER_TYPE_SYSTEM_HEADLESS, mUserManagerService.getUserTypeForUser(104));
+        assertTrue(mUserManagerService.isUserOfType(104, USER_TYPE_SYSTEM_HEADLESS));
 
-        assertEquals(USER_TYPE_FULL_SYSTEM, mUserManagerService.getUserTypeForUser(105));
+        assertTrue(mUserManagerService.isUserOfType(105, USER_TYPE_FULL_SYSTEM));
 
-        assertEquals(USER_TYPE_FULL_DEMO, mUserManagerService.getUserTypeForUser(106));
+        assertTrue(mUserManagerService.isUserOfType(106, USER_TYPE_FULL_DEMO));
     }
 
     /** Creates a UserInfo with the given flags and userType. */
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index a7275fc7..77376f0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -341,8 +341,8 @@
         assertThat(mUserManager.getUserBadgeNoBackgroundResId(userId))
                 .isEqualTo(userTypeDetails.getBadgeNoBackground());
         assertThat(mUserManager.isProfile(userId)).isEqualTo(userTypeDetails.isProfile());
-        assertThat(mUserManager.getUserTypeForUser(asHandle(userId)))
-                .isEqualTo(userTypeDetails.getName());
+        assertThat(mUserManager.isUserOfType(asHandle(userId), userTypeDetails.getName()))
+                .isTrue();
 
         final int badgeIndex = userInfo.profileBadge;
         assertThat(mUserManager.getUserBadgeColor(userId)).isEqualTo(
diff --git a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
new file mode 100644
index 0000000..7666ab9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.SensorManager;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ServiceManager;
+import android.os.Vibrator;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.power.batterysaver.BatterySaverPolicy;
+import com.android.server.power.batterysaver.BatterySavingStats;
+import com.android.server.statusbar.StatusBarManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link com.android.server.power.Notifier}
+ */
+public class NotifierTest {
+    private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
+    private static final int USER_ID = 0;
+
+    @Mock private BatterySaverPolicy mBatterySaverPolicyMock;
+    @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
+    @Mock private Notifier mNotifierMock;
+    @Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
+    @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
+    @Mock private SystemPropertiesWrapper mSystemPropertiesMock;
+    @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
+    @Mock private BatteryStatsImpl mBatteryStats;
+    @Mock private Vibrator mVibrator;
+    @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+
+    private PowerManagerService mService;
+    private Context mContextSpy;
+    private Resources mResourcesSpy;
+    private TestLooper mTestLooper = new TestLooper();
+    private Notifier mNotifier;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+        LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
+
+        mContextSpy = spy(new TestableContext(InstrumentationRegistry.getContext()));
+        mResourcesSpy = spy(mContextSpy.getResources());
+        when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
+        when(mContextSpy.getSystemService(Vibrator.class)).thenReturn(mVibrator);
+
+        mService = new PowerManagerService(mContextSpy, mInjector);
+    }
+
+    @Test
+    public void testVibrateEnabled_wiredCharging() {
+        createNotifier();
+
+        // GIVEN the charging vibration is enabled
+        enableChargingVibration(true);
+
+        // WHEN wired charging starts
+        mNotifier.onWiredChargingStarted(USER_ID);
+        mTestLooper.dispatchAll();
+
+        // THEN the device vibrates once
+        verify(mVibrator, times(1)).vibrate(any(), any());
+    }
+
+    @Test
+    public void testVibrateDisabled_wiredCharging() {
+        createNotifier();
+
+        // GIVEN the charging vibration is disabled
+        enableChargingVibration(false);
+
+        // WHEN wired charging starts
+        mNotifier.onWiredChargingStarted(USER_ID);
+        mTestLooper.dispatchAll();
+
+        // THEN the device doesn't vibrate
+        verify(mVibrator, never()).vibrate(any(), any());
+    }
+
+    @Test
+    public void testVibrateEnabled_wirelessCharging() {
+        createNotifier();
+
+        // GIVEN the charging vibration is enabled
+        enableChargingVibration(true);
+
+        // WHEN wireless charging starts
+        mNotifier.onWirelessChargingStarted(5, USER_ID);
+        mTestLooper.dispatchAll();
+
+        // THEN the device vibrates once
+        verify(mVibrator, times(1)).vibrate(any(), any());
+    }
+
+    @Test
+    public void testVibrateDisabled_wirelessCharging() {
+        createNotifier();
+
+        // GIVEN the charging vibration is disabeld
+        enableChargingVibration(false);
+
+        // WHEN wireless charging starts
+        mNotifier.onWirelessChargingStarted(5, USER_ID);
+        mTestLooper.dispatchAll();
+
+        // THEN the device doesn't vibrate
+        verify(mVibrator, never()).vibrate(any(), any());
+    }
+
+    @Test
+    public void testVibrateEnabled_dndOn() {
+        createNotifier();
+
+        // GIVEN the charging vibration is enabled but dnd is on
+        enableChargingVibration(true);
+        enableChargingFeedback(
+                /* chargingFeedbackEnabled */ true,
+                /* dndOn */ true);
+
+        // WHEN wired charging starts
+        mNotifier.onWiredChargingStarted(USER_ID);
+        mTestLooper.dispatchAll();
+
+        // THEN the device doesn't vibrate
+        verify(mVibrator, never()).vibrate(any(), any());
+    }
+
+    @Test
+    public void testWirelessAnimationEnabled() {
+        // GIVEN the wireless charging animation is enabled
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim))
+                .thenReturn(true);
+        createNotifier();
+
+        // WHEN wireless charging starts
+        mNotifier.onWirelessChargingStarted(5, USER_ID);
+        mTestLooper.dispatchAll();
+
+        // THEN the charging animation is triggered
+        verify(mStatusBarManagerInternal, times(1)).showChargingAnimation(5);
+    }
+
+    @Test
+    public void testWirelessAnimationDisabled() {
+        // GIVEN the wireless charging animation is disabled
+        when(mResourcesSpy.getBoolean(
+                com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim))
+                .thenReturn(false);
+        createNotifier();
+
+        // WHEN wireless charging starts
+        mNotifier.onWirelessChargingStarted(5, USER_ID);
+        mTestLooper.dispatchAll();
+
+        // THEN the charging animation never gets called
+        verify(mStatusBarManagerInternal, never()).showChargingAnimation(anyInt());
+    }
+
+    private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() {
+        @Override
+        Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
+                SuspendBlocker suspendBlocker, WindowManagerPolicy policy) {
+            return mNotifierMock;
+        }
+
+        @Override
+        SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
+            return super.createSuspendBlocker(service, name);
+        }
+
+        @Override
+        BatterySaverPolicy createBatterySaverPolicy(
+                Object lock, Context context, BatterySavingStats batterySavingStats) {
+            return mBatterySaverPolicyMock;
+        }
+
+        @Override
+        PowerManagerService.NativeWrapper createNativeWrapper() {
+            return mNativeWrapperMock;
+        }
+
+        @Override
+        WirelessChargerDetector createWirelessChargerDetector(
+                SensorManager sensorManager, SuspendBlocker suspendBlocker, Handler handler) {
+            return mWirelessChargerDetectorMock;
+        }
+
+        @Override
+        AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) {
+            return mAmbientDisplayConfigurationMock;
+        }
+
+        @Override
+        InattentiveSleepWarningController createInattentiveSleepWarningController() {
+            return mInattentiveSleepWarningControllerMock;
+        }
+
+        @Override
+        public SystemPropertiesWrapper createSystemPropertiesWrapper() {
+            return mSystemPropertiesMock;
+        }
+    };
+
+    private void enableChargingFeedback(boolean chargingFeedbackEnabled, boolean dndOn) {
+        // enable/disable charging feedback
+        Settings.Secure.putIntForUser(
+                mContextSpy.getContentResolver(),
+                Settings.Secure.CHARGING_SOUNDS_ENABLED,
+                chargingFeedbackEnabled ? 1 : 0,
+                USER_ID);
+
+        // toggle on/off dnd
+        Settings.Global.putInt(
+                mContextSpy.getContentResolver(),
+                Settings.Global.ZEN_MODE,
+                dndOn ? Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+                        : Settings.Global.ZEN_MODE_OFF);
+    }
+
+    private void enableChargingVibration(boolean enable) {
+        enableChargingFeedback(true, false);
+
+        Settings.Secure.putIntForUser(
+                mContextSpy.getContentResolver(),
+                Settings.Secure.CHARGING_VIBRATION_ENABLED,
+                enable ? 1 : 0,
+                USER_ID);
+    }
+
+    private void createNotifier() {
+        mNotifier = new Notifier(
+                mTestLooper.getLooper(),
+                mContextSpy,
+                IBatteryStats.Stub.asInterface(ServiceManager.getService(
+                        BatteryStats.SERVICE_NAME)),
+                mInjector.createSuspendBlocker(mService, "testBlocker"),
+                null);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
index 1f312bf..d5cdbeb 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
@@ -20,16 +20,19 @@
 import static org.junit.Assert.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
 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.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.IntentSender;
 import android.os.Handler;
 import android.os.IPowerManager;
 import android.os.IRecoverySystemProgressListener;
@@ -40,6 +43,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.widget.LockSettingsInternal;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -58,6 +63,7 @@
     private Context mContext;
     private IPowerManager mIPowerManager;
     private FileWriter mUncryptUpdateFileWriter;
+    private LockSettingsInternal mLockSettingsInternal;
 
     @Before
     public void setup() {
@@ -65,6 +71,9 @@
         mSystemProperties = new RecoverySystemServiceTestable.FakeSystemProperties();
         mUncryptSocket = mock(RecoverySystemService.UncryptSocket.class);
         mUncryptUpdateFileWriter = mock(FileWriter.class);
+        mLockSettingsInternal = mock(LockSettingsInternal.class);
+
+        when(mLockSettingsInternal.armRebootEscrow()).thenReturn(true);
 
         Looper looper = InstrumentationRegistry.getContext().getMainLooper();
         mIPowerManager = mock(IPowerManager.class);
@@ -72,7 +81,7 @@
                 new Handler(looper));
 
         mRecoverySystemService = new RecoverySystemServiceTestable(mContext, mSystemProperties,
-                powerManager, mUncryptUpdateFileWriter, mUncryptSocket);
+                powerManager, mUncryptUpdateFileWriter, mUncryptSocket, mLockSettingsInternal);
     }
 
     @Test
@@ -194,4 +203,100 @@
         verify(mUncryptSocket).sendAck();
         verify(mUncryptSocket).close();
     }
+
+    @Test(expected = SecurityException.class)
+    public void requestLskf_protected() {
+        doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.RECOVERY), any());
+        mRecoverySystemService.requestLskf("test", null);
+    }
+
+
+    @Test
+    public void requestLskf_nullToken_failure() {
+        assertThat(mRecoverySystemService.requestLskf(null, null), is(false));
+    }
+
+    @Test
+    public void requestLskf_success() throws Exception {
+        IntentSender intentSender = mock(IntentSender.class);
+        assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+        mRecoverySystemService.onPreparedForReboot(true);
+        verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+    }
+
+    @Test
+    public void requestLskf_subsequentRequestClearsPrepared() throws Exception {
+        IntentSender intentSender = mock(IntentSender.class);
+        assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+        mRecoverySystemService.onPreparedForReboot(true);
+        verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+
+        assertThat(mRecoverySystemService.requestLskf("test2", null), is(true));
+        assertThat(mRecoverySystemService.rebootWithLskf("test", null), is(false));
+        assertThat(mRecoverySystemService.rebootWithLskf("test2", "foobar"), is(false));
+
+        mRecoverySystemService.onPreparedForReboot(true);
+        assertThat(mRecoverySystemService.rebootWithLskf("test2", "foobar"), is(true));
+        verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+        verify(mIPowerManager).reboot(anyBoolean(), eq("foobar"), anyBoolean());
+    }
+
+
+    @Test
+    public void requestLskf_requestedButNotPrepared() throws Exception {
+        IntentSender intentSender = mock(IntentSender.class);
+        assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+        verify(intentSender, never()).sendIntent(any(), anyInt(), any(), any(), any());
+    }
+
+    @Test(expected = SecurityException.class)
+    public void clearLskf_protected() {
+        doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.RECOVERY), any());
+        mRecoverySystemService.clearLskf();
+    }
+
+    @Test
+    public void clearLskf_requestedThenCleared() throws Exception {
+        IntentSender intentSender = mock(IntentSender.class);
+        assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+        mRecoverySystemService.onPreparedForReboot(true);
+        verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+
+        assertThat(mRecoverySystemService.clearLskf(), is(true));
+        verify(mLockSettingsInternal).clearRebootEscrow();
+    }
+
+    @Test
+    public void startup_setRebootEscrowListener() throws Exception {
+        mRecoverySystemService.onSystemServicesReady();
+        verify(mLockSettingsInternal).setRebootEscrowListener(any());
+    }
+
+    @Test(expected = SecurityException.class)
+    public void rebootWithLskf_protected() {
+        doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.RECOVERY), any());
+        mRecoverySystemService.rebootWithLskf("test1", null);
+    }
+
+    @Test
+    public void rebootWithLskf_Success() throws Exception {
+        assertThat(mRecoverySystemService.requestLskf("test", null), is(true));
+        mRecoverySystemService.onPreparedForReboot(true);
+        assertThat(mRecoverySystemService.rebootWithLskf("test", "ab-update"), is(true));
+        verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean());
+    }
+
+    @Test
+    public void rebootWithLskf_withoutPrepare_Failure() throws Exception {
+        assertThat(mRecoverySystemService.rebootWithLskf("test1", null), is(false));
+    }
+
+    @Test
+    public void rebootWithLskf_withNullUpdateToken_Failure() throws Exception {
+        assertThat(mRecoverySystemService.rebootWithLskf(null, null), is(false));
+        verifyNoMoreInteractions(mIPowerManager);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
index a986b71..131e4f3 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 import android.os.PowerManager;
 
+import com.android.internal.widget.LockSettingsInternal;
+
 import java.io.FileWriter;
 
 public class RecoverySystemServiceTestable extends RecoverySystemService {
@@ -27,15 +29,17 @@
         private final PowerManager mPowerManager;
         private final FileWriter mUncryptPackageFileWriter;
         private final UncryptSocket mUncryptSocket;
+        private final LockSettingsInternal mLockSettingsInternal;
 
         MockInjector(Context context, FakeSystemProperties systemProperties,
                 PowerManager powerManager, FileWriter uncryptPackageFileWriter,
-                UncryptSocket uncryptSocket) {
+                UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal) {
             super(context);
             mSystemProperties = systemProperties;
             mPowerManager = powerManager;
             mUncryptPackageFileWriter = uncryptPackageFileWriter;
             mUncryptSocket = uncryptSocket;
+            mLockSettingsInternal = lockSettingsInternal;
         }
 
         @Override
@@ -76,13 +80,18 @@
         @Override
         public void threadSleep(long millis) {
         }
+
+        @Override
+        public LockSettingsInternal getLockSettingsService() {
+            return mLockSettingsInternal;
+        }
     }
 
     RecoverySystemServiceTestable(Context context, FakeSystemProperties systemProperties,
             PowerManager powerManager, FileWriter uncryptPackageFileWriter,
-            UncryptSocket uncryptSocket) {
+            UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal) {
         super(new MockInjector(context, systemProperties, powerManager, uncryptPackageFileWriter,
-                uncryptSocket));
+                uncryptSocket, lockSettingsInternal));
     }
 
     public static class FakeSystemProperties {
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 82f32f8..f8915c0 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -60,8 +60,6 @@
 import android.os.IHwInterface;
 import android.os.RemoteException;
 
-import androidx.test.runner.AndroidJUnit4;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -144,7 +142,11 @@
         properties.maxSoundModels = 456;
         properties.maxKeyPhrases = 567;
         properties.maxUsers = 678;
-        properties.recognitionModes = 789;
+        properties.recognitionModes =
+                android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+                | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION
+                | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
+                | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
         properties.captureTransition = true;
         properties.maxBufferMs = 321;
         properties.concurrentCapture = supportConcurrentCapture;
@@ -162,7 +164,10 @@
         assertEquals(456, properties.maxSoundModels);
         assertEquals(567, properties.maxKeyPhrases);
         assertEquals(678, properties.maxUsers);
-        assertEquals(789, properties.recognitionModes);
+        assertEquals(RecognitionMode.GENERIC_TRIGGER
+                | RecognitionMode.USER_AUTHENTICATION
+                | RecognitionMode.USER_IDENTIFICATION
+                | RecognitionMode.VOICE_TRIGGER, properties.recognitionModes);
         assertTrue(properties.captureTransition);
         assertEquals(321, properties.maxBufferMs);
         assertEquals(supportConcurrentCapture, properties.concurrentCapture);
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 71b568c..ae53692 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -37,7 +37,7 @@
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import androidx.test.runner.AndroidJUnit4;
 
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index ca6fd08..aaf9799 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -30,7 +30,7 @@
 import android.icu.util.Calendar;
 import android.icu.util.GregorianCalendar;
 import android.icu.util.TimeZone;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import androidx.test.runner.AndroidJUnit4;
 
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java
index 239d413..f1e9191 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java
@@ -18,7 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
 
 import androidx.test.runner.AndroidJUnit4;
 
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 95617b1..172df99 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -492,23 +492,13 @@
         return sbn;
     }
 
-
     private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
             String groupKey, boolean isSummary) {
-        return generateNotificationRecord(channel, id, groupKey, isSummary, false /* isBubble */);
-    }
-
-    private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
-            String groupKey, boolean isSummary, boolean isBubble) {
         Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
                 .setContentTitle("foo")
                 .setSmallIcon(android.R.drawable.sym_def_app_icon)
                 .setGroup(groupKey)
                 .setGroupSummary(isSummary);
-        if (isBubble) {
-            nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build());
-        }
-
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id,
                 "tag" + System.currentTimeMillis(), mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
@@ -521,11 +511,6 @@
 
     private NotificationRecord generateNotificationRecord(NotificationChannel channel,
             Notification.TvExtender extender) {
-        return generateNotificationRecord(channel, extender, false /* isBubble */);
-    }
-
-    private NotificationRecord generateNotificationRecord(NotificationChannel channel,
-            Notification.TvExtender extender, boolean isBubble) {
         if (channel == null) {
             channel = mTestNotificationChannel;
         }
@@ -535,9 +520,6 @@
         if (extender != null) {
             nb.extend(extender);
         }
-        if (isBubble) {
-            nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build());
-        }
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
@@ -555,6 +537,26 @@
         return new NotificationRecord(mContext, sbn, channel);
     }
 
+    private NotificationRecord generateMessageBubbleNotifRecord(NotificationChannel channel,
+            String tag) {
+        return generateMessageBubbleNotifRecord(true, channel, 1, tag, null, false);
+    }
+
+    private NotificationRecord generateMessageBubbleNotifRecord(boolean addMetadata,
+            NotificationChannel channel, int id, String tag, String groupKey, boolean isSummary) {
+        if (channel == null) {
+            channel = mTestNotificationChannel;
+        }
+        if (tag == null) {
+            tag = "tag";
+        }
+        Notification.Builder nb = getMessageStyleNotifBuilder(addMetadata, groupKey, isSummary);
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id,
+                tag, mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        return new NotificationRecord(mContext, sbn, channel);
+    }
+
     private Map<String, Answer> getSignalExtractorSideEffects() {
         Map<String, Answer> answers = new ArrayMap<>();
 
@@ -610,23 +612,57 @@
                 false);
     }
 
-    private Notification.BubbleMetadata.Builder getBasicBubbleMetadataBuilder() {
+    private Notification.BubbleMetadata.Builder getBubbleMetadataBuilder() {
         PendingIntent pi = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
         return new Notification.BubbleMetadata.Builder()
                 .setIntent(pi)
                 .setIcon(Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon));
     }
 
+    private Notification.Builder getMessageStyleNotifBuilder(boolean addBubbleMetadata,
+            String groupKey, boolean isSummary) {
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // It needs remote input to be bubble-able
+        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+                inputIntent).addRemoteInput(remoteInput)
+                .build();
+        // Make it messaging style
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person)
+                )
+                .setActions(replyAction)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setGroupSummary(isSummary);
+        if (groupKey != null) {
+            nb.setGroup(groupKey);
+        }
+        if (addBubbleMetadata) {
+            nb.setBubbleMetadata(getBubbleMetadataBuilder().build());
+        }
+        return nb;
+    }
+
     private NotificationRecord addGroupWithBubblesAndValidateAdded(boolean summaryAutoCancel)
             throws RemoteException {
 
-        // Notification that has bubble metadata
-        NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel, 1,
-                "BUBBLE_GROUP", false /* isSummary */, true /* isBubble */);
+        String groupKey = "BUBBLE_GROUP";
 
-        // Make the package foreground so that we're allowed to be a bubble
-        when(mActivityManager.getPackageImportance(nrBubble.sbn.getPackageName())).thenReturn(
-                IMPORTANCE_FOREGROUND);
+        // Notification that has bubble metadata
+        NotificationRecord nrBubble = generateMessageBubbleNotifRecord(true /* addMetadata */,
+                mTestNotificationChannel, 1 /* id */, "tag", groupKey, false /* isSummary */);
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nrBubble.sbn.getTag(),
                 nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId());
@@ -637,9 +673,10 @@
         assertEquals(1, notifsAfter.length);
         assertTrue((notifsAfter[0].getNotification().flags & FLAG_BUBBLE) != 0);
 
-        // Plain notification without bubble metadata
-        NotificationRecord nrPlain = generateNotificationRecord(mTestNotificationChannel, 2,
-                "BUBBLE_GROUP", false /* isSummary */, false /* isBubble */);
+        // Notification without bubble metadata
+        NotificationRecord nrPlain = generateMessageBubbleNotifRecord(false /* addMetadata */,
+                mTestNotificationChannel, 2 /* id */, "tag", groupKey, false /* isSummary */);
+
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nrPlain.sbn.getTag(),
                 nrPlain.sbn.getId(), nrPlain.sbn.getNotification(), nrPlain.sbn.getUserId());
         waitForIdle();
@@ -648,8 +685,9 @@
         assertEquals(2, notifsAfter.length);
 
         // Summary notification for both of those
-        NotificationRecord nrSummary = generateNotificationRecord(mTestNotificationChannel, 3,
-                "BUBBLE_GROUP", true /* isSummary */, false /* isBubble */);
+        NotificationRecord nrSummary = generateMessageBubbleNotifRecord(false /* addMetadata */,
+                mTestNotificationChannel, 3 /* id */, "tag", groupKey, true /* isSummary */);
+
         if (summaryAutoCancel) {
             nrSummary.getNotification().flags |= FLAG_AUTO_CANCEL;
         }
@@ -4708,13 +4746,8 @@
         // Bubbles are allowed!
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
 
-        // Notif with bubble metadata but not our other misc requirements
-        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
-                null /* tvExtender */, true /* isBubble */);
-
-        // Say we're foreground
-        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
-                IMPORTANCE_FOREGROUND);
+        NotificationRecord nr =
+                generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble");
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
@@ -4732,13 +4765,8 @@
         // Bubbles are allowed!
         setUpPrefsForBubbles(true /* global */, false /* app */, true /* channel */);
 
-        // Notif with bubble metadata but not our other misc requirements
-        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
-                null /* tvExtender */, true /* isBubble */);
-
-        // Say we're foreground
-        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
-                IMPORTANCE_FOREGROUND);
+        NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+                        "testFlagBubble_noFlag_appNotAllowed");
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
@@ -4752,174 +4780,40 @@
     }
 
     @Test
-    public void testFlagBubbleNotifs_flag_appForeground() throws RemoteException {
+    public void testFlagBubbleNotifs_noFlag_whenAppForeground() throws RemoteException {
         // Bubbles are allowed!
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
 
         // Notif with bubble metadata but not our other misc requirements
-        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
-                null /* tvExtender */, true /* isBubble */);
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setBubbleMetadata(getBubbleMetadataBuilder().build());
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         // Say we're foreground
         when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
-
-        mBinderService.enqueueNotificationWithTag(PKG, PKG,
-                nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
-        waitForIdle();
-
-        // yes allowed, yes foreground, yes bubble
-        assertTrue(mService.getNotificationRecord(
-                nr.sbn.getKey()).getNotification().isBubbleNotification());
-    }
-
-    @Test
-    public void testFlagBubbleNotifs_noFlag_appNotForeground() throws RemoteException {
-        // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
-
-        // Notif with bubble metadata but not our other misc requirements
-        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
-                null /* tvExtender */, true /* isBubble */);
-
-        // Make sure we're NOT foreground
-        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
-                IMPORTANCE_VISIBLE);
-
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
-        // yes allowed but NOT foreground, no bubble
+        // if notif isn't configured properly it doesn't get to bubble just because app is
+        // foreground.
         assertFalse(mService.getNotificationRecord(
                 nr.sbn.getKey()).getNotification().isBubbleNotification());
     }
 
     @Test
-    public void testFlagBubbleNotifs_flag_previousForegroundFlag() throws RemoteException {
-        // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
-
-        // Notif with bubble metadata but not our other misc requirements
-        NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
-                null /* tvExtender */, true /* isBubble */);
-
-        // Send notif when we're foreground
-        when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn(
-                IMPORTANCE_FOREGROUND);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr1.sbn.getTag(),
-                nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId());
-        waitForIdle();
-
-        // yes allowed, yes foreground, yes bubble
-        assertTrue(mService.getNotificationRecord(
-                nr1.sbn.getKey()).getNotification().isBubbleNotification());
-
-        // Send a new update when we're not foreground
-        NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel,
-                null /* tvExtender */, true /* isBubble */);
-
-        when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn(
-                IMPORTANCE_VISIBLE);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(),
-                nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
-        waitForIdle();
-
-        // yes allowed, previously foreground / flagged, yes bubble
-        assertTrue(mService.getNotificationRecord(
-                nr2.sbn.getKey()).getNotification().isBubbleNotification());
-
-        StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG);
-        assertEquals(1, notifs2.length);
-        assertEquals(1, mService.getNotificationRecordCount());
-    }
-
-    @Test
-    public void testFlagBubbleNotifs_noFlag_previousForegroundFlag_afterRemoval()
-            throws RemoteException {
-        // Bubbles are allowed!
-        setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
-
-        // Notif with bubble metadata but not our other misc requirements
-        NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
-                null /* tvExtender */, true /* isBubble */);
-
-        // Send notif when we're foreground
-        when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn(
-                IMPORTANCE_FOREGROUND);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr1.sbn.getTag(),
-                nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId());
-        waitForIdle();
-
-        // yes allowed, yes foreground, yes bubble
-        assertTrue(mService.getNotificationRecord(
-                nr1.sbn.getKey()).getNotification().isBubbleNotification());
-
-        // Remove the bubble
-        mBinderService.cancelNotificationWithTag(PKG, PKG, nr1.sbn.getTag(), nr1.sbn.getId(),
-                nr1.sbn.getUserId());
-        waitForIdle();
-
-        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
-        assertEquals(0, notifs.length);
-        assertEquals(0, mService.getNotificationRecordCount());
-
-        // Send a new update when we're not foreground
-        NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel,
-                null /* tvExtender */, true /* isBubble */);
-
-        when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn(
-                IMPORTANCE_VISIBLE);
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(),
-                nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
-        waitForIdle();
-
-        // yes allowed, but was removed & no foreground, so no bubble
-        assertFalse(mService.getNotificationRecord(
-                nr2.sbn.getKey()).getNotification().isBubbleNotification());
-
-        StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG);
-        assertEquals(1, notifs2.length);
-        assertEquals(1, mService.getNotificationRecordCount());
-    }
-
-    @Test
     public void testFlagBubbleNotifs_flag_messaging() throws RemoteException {
         // Bubbles are allowed!
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
 
-        // Give it bubble metadata
-        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
-        // Give it a person
-        Person person = new Person.Builder()
-                .setName("bubblebot")
-                .build();
-        // It needs remote input to be bubble-able
-        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
-        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
-        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                inputIntent).addRemoteInput(remoteInput)
-                .build();
-        // Make it messaging style
-        Notification.Builder nb = new Notification.Builder(mContext,
-                mTestNotificationChannel.getId())
-                .setContentTitle("foo")
-                .setBubbleMetadata(data)
-                .setStyle(new Notification.MessagingStyle(person)
-                        .setConversationTitle("Bubble Chat")
-                        .addMessage("Hello?",
-                                SystemClock.currentThreadTimeMillis() - 300000, person)
-                        .addMessage("Is it me you're looking for?",
-                                SystemClock.currentThreadTimeMillis(), person)
-                )
-                .setActions(replyAction)
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
-                "testFlagBubbleNotifs_flag_messaging", mUid, 0,
-                nb.build(), new UserHandle(mUid), null, 0);
-        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+        NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+                "testFlagBubbleNotifs_flag_messaging");
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
@@ -4927,7 +4821,7 @@
 
         // yes allowed, yes messaging, yes bubble
         assertTrue(mService.getNotificationRecord(
-                sbn.getKey()).getNotification().isBubbleNotification());
+                nr.sbn.getKey()).getNotification().isBubbleNotification());
     }
 
     @Test
@@ -4936,7 +4830,7 @@
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
-        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
         // Give it a person
         Person person = new Person.Builder()
                 .setName("bubblebot")
@@ -4972,7 +4866,7 @@
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
-        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
         // Give it a person
         Person person = new Person.Builder()
                 .setName("bubblebot")
@@ -5005,7 +4899,7 @@
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
-        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
         // Make it a phone call
         Notification.Builder nb = new Notification.Builder(mContext,
                 mTestNotificationChannel.getId())
@@ -5036,7 +4930,7 @@
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
-        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
         // Give it a person
         Person person = new Person.Builder()
                 .setName("bubblebot")
@@ -5070,30 +4964,8 @@
         // Bubbles are NOT allowed!
         setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
 
-        // Give it bubble metadata
-        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
-        // Give it a person
-        Person person = new Person.Builder()
-                .setName("bubblebot")
-                .build();
-        // Make it messaging style
-        Notification.Builder nb = new Notification.Builder(mContext,
-                mTestNotificationChannel.getId())
-                .setContentTitle("foo")
-                .setBubbleMetadata(data)
-                .setStyle(new Notification.MessagingStyle(person)
-                        .setConversationTitle("Bubble Chat")
-                        .addMessage("Hello?",
-                                SystemClock.currentThreadTimeMillis() - 300000, person)
-                        .addMessage("Is it me you're looking for?",
-                                SystemClock.currentThreadTimeMillis(), person)
-                )
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
-                "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed", mUid, 0,
-                nb.build(), new UserHandle(mUid), null, 0);
-        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+        NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+                "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed");
 
         // Post the notification
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
@@ -5102,7 +4974,7 @@
 
         // not allowed, no bubble
         assertFalse(mService.getNotificationRecord(
-                sbn.getKey()).getNotification().isBubbleNotification());
+                nr.sbn.getKey()).getNotification().isBubbleNotification());
     }
 
     @Test
@@ -5110,8 +4982,14 @@
         // Bubbles are allowed!
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
 
-        // Notif WITHOUT bubble metadata
-        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+        // Messaging notif WITHOUT bubble metadata
+        Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */,
+                null /* groupKey */, false /* isSummary */);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "testFlagBubbleNotifs_noFlag_notBubble", mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
 
         // Post the notification
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
@@ -5128,39 +5006,17 @@
         // Bubbles are allowed except on this channel
         setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
 
-        // Give it bubble metadata
-        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
-        // Give it a person
-        Person person = new Person.Builder()
-                .setName("bubblebot")
-                .build();
-        // Make it messaging style
-        Notification.Builder nb = new Notification.Builder(mContext,
-                mTestNotificationChannel.getId())
-                .setContentTitle("foo")
-                .setBubbleMetadata(data)
-                .setStyle(new Notification.MessagingStyle(person)
-                        .setConversationTitle("Bubble Chat")
-                        .addMessage("Hello?",
-                                SystemClock.currentThreadTimeMillis() - 300000, person)
-                        .addMessage("Is it me you're looking for?",
-                                SystemClock.currentThreadTimeMillis(), person)
-                )
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
-                "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed", mUid, 0,
-                nb.build(), new UserHandle(mUid), null, 0);
-        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+        NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+                "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed");
 
         // Post the notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
         // channel not allowed, no bubble
         assertFalse(mService.getNotificationRecord(
-                sbn.getKey()).getNotification().isBubbleNotification());
+                nr.sbn.getKey()).getNotification().isBubbleNotification());
     }
 
     @Test
@@ -5169,7 +5025,7 @@
         setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
 
         // Give it bubble metadata
-        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
         // Give it a person
         Person person = new Person.Builder()
                 .setName("bubblebot")
@@ -5205,7 +5061,7 @@
         setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
 
         // Give it bubble metadata
-        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
         // Give it a person
         Person person = new Person.Builder()
                 .setName("bubblebot")
@@ -5412,13 +5268,9 @@
         // Bubbles are allowed!
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
 
-        // Notif with bubble metadata but not our other misc requirements
-        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
-                null /* tvExtender */, true /* isBubble */);
-
-        // Say we're foreground
-        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
-                IMPORTANCE_FOREGROUND);
+        // Notif with bubble metadata
+        NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+                "testNotificationBubbleChanged_false");
 
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
@@ -5447,9 +5299,9 @@
         // Bubbles are allowed!
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
 
-        // Plain notification that has bubble metadata
+        // Notif that is not a bubble
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
-                null /* tvExtender */, true /* isBubble */);
+                1, null, false);
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
@@ -5459,9 +5311,12 @@
         assertEquals(1, notifsBefore.length);
         assertEquals((notifsBefore[0].getNotification().flags & FLAG_BUBBLE), 0);
 
-        // Make the package foreground so that we're allowed to be a bubble
-        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
-                IMPORTANCE_FOREGROUND);
+        // Update the notification to be message style / meet bubble requirements
+        NotificationRecord nr2 = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+                nr.sbn.getTag());
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(),
+                nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
+        waitForIdle();
 
         // Reset as this is called when the notif is first sent
         reset(mListeners);
@@ -5482,8 +5337,7 @@
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
 
         // Notif that is not a bubble
-        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
-                null /* tvExtender */, true /* isBubble */);
+        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
@@ -5681,26 +5535,17 @@
         // Bubbles are allowed!
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
 
-        // Plain notification that has bubble metadata
-        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
-                null /* tvExtender */, true /* isBubble */);
+        // And we are low ram
+        when(mActivityManager.isLowRamDevice()).thenReturn(true);
+
+        // Notification that would typically bubble
+        NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+                "testNotificationBubbles_disabled_lowRamDevice");
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
-        // Would be a normal notification because wouldn't have met requirements to bubble
-        StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
-        assertEquals(1, notifsBefore.length);
-        assertEquals((notifsBefore[0].getNotification().flags & FLAG_BUBBLE), 0);
-
-        // Make the package foreground so that we're allowed to be a bubble
-        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
-                IMPORTANCE_FOREGROUND);
-
-        // And we are low ram
-        when(mActivityManager.isLowRamDevice()).thenReturn(true);
-
-        // We wouldn't be a bubble because the notification didn't meet requirements (low ram)
+        // But we wouldn't be a bubble because the device is low ram & all bubbles are disabled.
         StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
         assertEquals(1, notifsAfter.length);
         assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0);
@@ -5774,50 +5619,23 @@
         // Bubbles are allowed!
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
 
-        // Give it bubble metadata
-        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder()
-                .setSuppressNotification(true)
-                .setAutoExpandBubble(true).build();
-        // Give it a person
-        Person person = new Person.Builder()
-                .setName("bubblebot")
-                .build();
-        // It needs remote input to be bubble-able
-        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
-        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
-        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                inputIntent).addRemoteInput(remoteInput)
-                .build();
-        // Make it messaging style
-        Notification.Builder nb = new Notification.Builder(mContext,
-                mTestNotificationChannel.getId())
-                .setContentTitle("foo")
-                .setBubbleMetadata(data)
-                .setStyle(new Notification.MessagingStyle(person)
-                        .setConversationTitle("Bubble Chat")
-                        .addMessage("Hello?",
-                                SystemClock.currentThreadTimeMillis() - 300000, person)
-                        .addMessage("Is it me you're looking for?",
-                                SystemClock.currentThreadTimeMillis(), person)
-                )
-                .setActions(replyAction)
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
-                nb.build(), new UserHandle(mUid), null, 0);
-        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+        NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+                "testNotificationBubbles_flagAutoExpandForeground_fails_notForeground");
+        // Modify metadata flags
+        nr.sbn.getNotification().getBubbleMetadata().setFlags(
+                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE
+                        | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
 
         // Ensure we're not foreground
         when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_VISIBLE);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
         // yes allowed, yes messaging, yes bubble
-        Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification();
+        Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification();
         assertTrue(notif.isBubbleNotification());
 
         // Our flags should have failed since we're not foreground
@@ -5831,53 +5649,26 @@
         // Bubbles are allowed!
         setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
 
-        // Give it bubble metadata
-        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder()
-                .setSuppressNotification(true)
-                .setAutoExpandBubble(true).build();
-        // Give it a person
-        Person person = new Person.Builder()
-                .setName("bubblebot")
-                .build();
-        // It needs remote input to be bubble-able
-        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
-        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
-        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                inputIntent).addRemoteInput(remoteInput)
-                .build();
-        // Make it messaging style
-        Notification.Builder nb = new Notification.Builder(mContext,
-                mTestNotificationChannel.getId())
-                .setContentTitle("foo")
-                .setBubbleMetadata(data)
-                .setStyle(new Notification.MessagingStyle(person)
-                        .setConversationTitle("Bubble Chat")
-                        .addMessage("Hello?",
-                                SystemClock.currentThreadTimeMillis() - 300000, person)
-                        .addMessage("Is it me you're looking for?",
-                                SystemClock.currentThreadTimeMillis(), person)
-                )
-                .setActions(replyAction)
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
-                nb.build(), new UserHandle(mUid), null, 0);
-        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+        NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+                "testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground");
+        // Modify metadata flags
+        nr.sbn.getNotification().getBubbleMetadata().setFlags(
+                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE
+                        | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
 
         // Ensure we are in the foreground
         when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
                 IMPORTANCE_FOREGROUND);
 
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
         // yes allowed, yes messaging, yes bubble
-        Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification();
+        Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification();
         assertTrue(notif.isBubbleNotification());
 
-        // Our flags should have failed since we are foreground
+        // Our flags should have passed since we are foreground
         assertTrue(notif.getBubbleMetadata().getAutoExpandBubble());
         assertTrue(notif.getBubbleMetadata().isNotificationSuppressed());
     }
@@ -6004,7 +5795,7 @@
     @Test
     public void testNotificationHistory_addNoisyNotification() throws Exception {
         NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
-                null /* tvExtender */, false);
+                null /* tvExtender */);
         mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 73dd2df..f2ba97c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -57,6 +57,8 @@
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -86,6 +88,7 @@
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.ViewRootImpl;
+import android.view.WindowManager;
 import android.view.test.InsetsModeSession;
 
 import androidx.test.filters.SmallTest;
@@ -561,8 +564,7 @@
         final DisplayContent dc = createNewDisplay();
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
 
-        dc.setLayoutNeeded();
-        dc.performLayout(true /* initial */, false /* updateImeWindows */);
+        performLayout(dc);
 
         assertThat(win.mLayoutSeq, is(dc.mLayoutSeq));
     }
@@ -829,8 +831,7 @@
         win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
         win.setSystemGestureExclusion(Collections.singletonList(new Rect(10, 20, 30, 40)));
 
-        dc.setLayoutNeeded();
-        dc.performLayout(true /* initial */, false /* updateImeWindows */);
+        performLayout(dc);
 
         win.setHasSurface(true);
         dc.updateSystemGestureExclusion();
@@ -866,8 +867,7 @@
         win2.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
         win2.setSystemGestureExclusion(Collections.singletonList(new Rect(20, 30, 40, 50)));
 
-        dc.setLayoutNeeded();
-        dc.performLayout(true /* initial */, false /* updateImeWindows */);
+        performLayout(dc);
 
         win.setHasSurface(true);
         win2.setHasSurface(true);
@@ -898,8 +898,7 @@
         win2.getAttrs().height = 10;
         win2.setSystemGestureExclusion(Collections.emptyList());
 
-        dc.setLayoutNeeded();
-        dc.performLayout(true /* initial */, false /* updateImeWindows */);
+        performLayout(dc);
 
         win.setHasSurface(true);
         win2.setHasSurface(true);
@@ -922,8 +921,7 @@
                         | SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
         win.mActivityRecord.mTargetSdk = P;
 
-        dc.setLayoutNeeded();
-        dc.performLayout(true /* initial */, false /* updateImeWindows */);
+        performLayout(dc);
 
         win.setHasSurface(true);
 
@@ -935,6 +933,22 @@
     }
 
     @Test
+    public void testRequestResizeForEmptyFrames() {
+        final WindowState win = mChildAppWindowAbove;
+        makeWindowVisible(win, win.getParentWindow());
+        win.setRequestedSize(mDisplayContent.mBaseDisplayWidth, 0 /* height */);
+        win.mAttrs.width = win.mAttrs.height = WindowManager.LayoutParams.WRAP_CONTENT;
+        win.mAttrs.gravity = Gravity.CENTER;
+        performLayout(mDisplayContent);
+
+        // The frame is empty because the requested height is zero.
+        assertTrue(win.getFrameLw().isEmpty());
+        // The window should be scheduled to resize then the client may report a new non-empty size.
+        win.updateResizingWindowIfNeeded();
+        assertThat(mWm.mResizingWindows).contains(win);
+    }
+
+    @Test
     public void testOrientationChangeLogging() {
         MetricsLogger mockLogger = mock(MetricsLogger.class);
         Configuration oldConfig = new Configuration();
@@ -1011,6 +1025,11 @@
         mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateInputWindows */);
     }
 
+    private void performLayout(DisplayContent dc) {
+        dc.setLayoutNeeded();
+        dc.performLayout(true /* initial */, false /* updateImeWindows */);
+    }
+
     /**
      * Create DisplayContent that does not update display base/initial values from device to keep
      * the values set by test.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 5aece45..0527561 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -594,6 +594,8 @@
 
     @Test
     public void layoutWindowLw_withForwardInset_SoftInputAdjustResize() {
+        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_NONE);
+
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
         mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 05d1c76..e507508 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -408,6 +408,16 @@
         }
     }
 
+    /**
+     * Throws if caller doesn't hold the given lock.
+     * @param lock the lock
+     */
+    static void checkHoldsLock(Object lock) {
+        if (!Thread.holdsLock(lock)) {
+            throw new IllegalStateException("Caller doesn't hold global lock.");
+        }
+    }
+
     protected class TestActivityTaskManagerService extends ActivityTaskManagerService {
         // ActivityStackSupervisor may be created more than once while setting up AMS and ATMS.
         // We keep the reference in order to prevent creating it twice.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index e5121b9..77af5ee 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -72,7 +72,7 @@
         private final DisplayInfo mInfo;
         private boolean mCanRotate = true;
         private int mWindowingMode = WINDOWING_MODE_FULLSCREEN;
-        private int mPosition = POSITION_TOP;
+        private int mPosition = POSITION_BOTTOM;
         private final ActivityTaskManagerService mService;
         private boolean mSystemDecorations = false;
 
@@ -127,13 +127,13 @@
             return this;
         }
         TestDisplayContent build() {
+            SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock);
+
             final int displayId = SystemServicesTestRule.sNextDisplayId++;
             final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
                     mInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
-            final TestDisplayContent newDisplay;
-            synchronized (mService.mGlobalLock) {
-                newDisplay = new TestDisplayContent(mService.mStackSupervisor, display);
-            }
+            final TestDisplayContent newDisplay =
+                    new TestDisplayContent(mService.mStackSupervisor, display);
             // disable the normal system decorations
             final DisplayPolicy displayPolicy = newDisplay.mDisplayContent.getDisplayPolicy();
             spyOn(displayPolicy);
@@ -153,6 +153,10 @@
                 doReturn(false).when(newDisplay.mDisplayContent)
                         .handlesOrientationChangeFromDescendant();
             }
+            // Please add stubbing before this line. Services will start using this display in other
+            // threads immediately after adding it to hierarchy. Calling doAnswer() type of stubbing
+            // reduces chance of races, but still doesn't eliminate race conditions.
+            mService.mRootWindowContainer.addChild(newDisplay, mPosition);
             return newDisplay;
         }
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ea88315..31a7f24 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -44,6 +44,7 @@
 import android.view.DisplayInfo;
 import android.view.IWindow;
 import android.view.SurfaceControl.Transaction;
+import android.view.View;
 import android.view.WindowManager;
 
 import com.android.server.AttributeCache;
@@ -312,6 +313,16 @@
         }
     }
 
+    static void makeWindowVisible(WindowState... windows) {
+        for (WindowState win : windows) {
+            win.mViewVisibility = View.VISIBLE;
+            win.mRelayoutCalled = true;
+            win.mHasSurface = true;
+            win.mHidden = false;
+            win.showLw(false /* doAnimation */, false /* requestAnim */);
+        }
+    }
+
     /** Creates a {@link ActivityStack} and adds it to the specified {@link DisplayContent}. */
     ActivityStack createTaskStackOnDisplay(DisplayContent dc) {
         return createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, dc);
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java
new file mode 100644
index 0000000..9cda084
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/RotationAnimationUtilsTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.utils;
+
+import static android.graphics.Bitmap.Config.ARGB_8888;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.view.Surface;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class RotationAnimationUtilsTest {
+
+    private static final int BITMAP_HEIGHT = 100;
+    private static final int BITMAP_WIDTH = 100;
+    private static final int POINT_WIDTH = 1000;
+    private static final int POINT_HEIGHT = 2000;
+
+    private ColorSpace mColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
+    private Matrix mMatrix;
+
+    @Before
+    public void setup() {
+        mMatrix = new Matrix();
+    }
+
+    @Test
+    public void blackLuma() {
+        Bitmap swBitmap = createBitmap(0);
+        GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap);
+        float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace);
+        assertEquals(0, borderLuma, 0);
+    }
+
+    @Test
+    public void whiteLuma() {
+        Bitmap swBitmap = createBitmap(1);
+        GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap);
+        float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace);
+        assertEquals(1, borderLuma, 0);
+    }
+
+    @Test
+    public void whiteImageBlackBorderLuma() {
+        Bitmap swBitmap = createBitmap(1);
+        setBorderLuma(swBitmap, 0);
+        GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap);
+        float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace);
+        assertEquals(0, borderLuma, 0);
+    }
+
+    @Test
+    public void blackImageWhiteBorderLuma() {
+        Bitmap swBitmap = createBitmap(0);
+        setBorderLuma(swBitmap, 1);
+        GraphicBuffer gb = swBitmapToGraphicsBuffer(swBitmap);
+        float borderLuma = RotationAnimationUtils.getAvgBorderLuma(gb, mColorSpace);
+        assertEquals(1, borderLuma, 0);
+    }
+
+    @Test
+    public void rotate_0_bottomRight() {
+        RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_0,
+                POINT_WIDTH, POINT_HEIGHT, mMatrix);
+        PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
+        assertEquals(POINT_WIDTH, newPoints.x, 0);
+        assertEquals(POINT_HEIGHT, newPoints.y, 0);
+    }
+
+    @Test
+    public void rotate_90_bottomRight() {
+        RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_90,
+                POINT_WIDTH, POINT_HEIGHT, mMatrix);
+        PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
+        assertEquals(0, newPoints.x, 0);
+        assertEquals(POINT_WIDTH, newPoints.y, 0);
+    }
+
+    @Test
+    public void rotate_180_bottomRight() {
+        RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_180,
+                POINT_WIDTH, POINT_HEIGHT, mMatrix);
+        PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
+        assertEquals(0, newPoints.x, 0);
+        assertEquals(0, newPoints.y, 0);
+    }
+
+    @Test
+    public void rotate_270_bottomRight() {
+        RotationAnimationUtils.createRotationMatrix(Surface.ROTATION_270,
+                POINT_WIDTH, POINT_HEIGHT, mMatrix);
+        PointF newPoints = checkMappedPoints(POINT_WIDTH, POINT_HEIGHT);
+        assertEquals(POINT_HEIGHT, newPoints.x, 0);
+        assertEquals(0, newPoints.y, 0);
+    }
+
+    private PointF checkMappedPoints(int x, int y) {
+        final float[] fs = new float[] {x, y};
+        mMatrix.mapPoints(fs);
+        return new PointF(fs[0], fs[1]);
+    }
+
+    private Bitmap createBitmap(float luma) {
+        Bitmap bitmap = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, ARGB_8888);
+        for (int i = 0; i < BITMAP_WIDTH; i++) {
+            for (int j = 0; j < BITMAP_HEIGHT; j++) {
+                bitmap.setPixel(i, j, Color.argb(1, luma, luma, luma));
+            }
+        }
+        return bitmap;
+    }
+
+    private GraphicBuffer swBitmapToGraphicsBuffer(Bitmap swBitmap) {
+        Bitmap hwBitmap = swBitmap.copy(Bitmap.Config.HARDWARE, false);
+        return hwBitmap.createGraphicBufferHandle();
+    }
+
+    private void setBorderLuma(Bitmap swBitmap, float luma) {
+        int i;
+        int width = swBitmap.getWidth();
+        int height = swBitmap.getHeight();
+        for (i = 0; i < width; i++) {
+            swBitmap.setPixel(i, 0, Color.argb(1, luma, luma, luma));
+            swBitmap.setPixel(i, height - 1, Color.argb(1, luma, luma, luma));
+        }
+        for (i = 0; i < height; i++) {
+            swBitmap.setPixel(0, i, Color.argb(1, luma, luma, luma));
+            swBitmap.setPixel(width - 1, i, Color.argb(1, luma, luma, luma));
+        }
+    }
+}
diff --git a/services/usage/OWNERS b/services/usage/OWNERS
new file mode 100644
index 0000000..9daa093
--- /dev/null
+++ b/services/usage/OWNERS
@@ -0,0 +1,4 @@
+mwachens@google.com
+varunshah@google.com
+huiyu@google.com
+yamasani@google.com
diff --git a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
index b75510b..488ee78 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
@@ -83,7 +83,7 @@
  * could transition to INTENT_STARTED.
  *
  * <p> If any bad transition happened, the state becomse UNKNOWN. The UNKNOWN state
- * could be * accumulated, because during the UNKNOWN state more IntentStarted may
+ * could be accumulated, because during the UNKNOWN state more IntentStarted may
  * be triggered. To recover from UNKNOWN to INIT, all the accumualted IntentStarted
  * should termniate.
  *
@@ -100,7 +100,7 @@
   @Override
   public void onIntentStarted(@NonNull Intent intent, long timestampNs) {
     if (state == State.UNKNOWN) {
-      Log.e(TAG, "IntentStarted during UNKNOWN." + intent);
+      Log.wtf(TAG, "IntentStarted during UNKNOWN." + intent);
       incAccIntentStartedEvents();
       return;
     }
@@ -110,32 +110,32 @@
         state != State.ACTIVITY_CANCELLED &&
         state != State.ACTIVITY_FINISHED &&
         state != State.REPORT_FULLY_DRAWN) {
-      Log.e(TAG,
+      Log.wtf(TAG,
           String.format("Cannot transition from %s to %s", state, State.INTENT_STARTED));
       incAccIntentStartedEvents();
       incAccIntentStartedEvents();
       return;
     }
 
-    Log.i(TAG, String.format("Tansition from %s to %s", state, State.INTENT_STARTED));
+    Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_STARTED));
     state = State.INTENT_STARTED;
   }
 
   @Override
   public void onIntentFailed() {
     if (state == State.UNKNOWN) {
-      Log.e(TAG, "IntentFailed during UNKNOWN.");
+      Log.wtf(TAG, "IntentFailed during UNKNOWN.");
       decAccIntentStartedEvents();
       return;
     }
     if (state != State.INTENT_STARTED) {
-      Log.e(TAG,
+      Log.wtf(TAG,
           String.format("Cannot transition from %s to %s", state, State.INTENT_FAILED));
       incAccIntentStartedEvents();
       return;
     }
 
-    Log.i(TAG, String.format("Tansition from %s to %s", state, State.INTENT_FAILED));
+    Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_FAILED));
     state = State.INTENT_FAILED;
   }
 
@@ -143,11 +143,11 @@
   public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity,
       @Temperature int temperature) {
     if (state == State.UNKNOWN) {
-      Log.e(TAG, "onActivityLaunched during UNKNOWN.");
+      Log.wtf(TAG, "onActivityLaunched during UNKNOWN.");
       return;
     }
     if (state != State.INTENT_STARTED) {
-      Log.e(TAG,
+      Log.wtf(TAG,
           String.format("Cannot transition from %s to %s", state, State.ACTIVITY_LAUNCHED));
       incAccIntentStartedEvents();
       return;
@@ -160,12 +160,12 @@
   @Override
   public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) {
     if (state == State.UNKNOWN) {
-      Log.e(TAG, "onActivityLaunchCancelled during UNKNOWN.");
+      Log.wtf(TAG, "onActivityLaunchCancelled during UNKNOWN.");
       decAccIntentStartedEvents();
       return;
     }
     if (state != State.ACTIVITY_LAUNCHED) {
-      Log.e(TAG,
+      Log.wtf(TAG,
           String.format("Cannot transition from %s to %s", state, State.ACTIVITY_CANCELLED));
       incAccIntentStartedEvents();
       return;
@@ -179,13 +179,13 @@
   public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity,
       long timestampNs) {
     if (state == State.UNKNOWN) {
-      Log.e(TAG, "onActivityLaunchFinished during UNKNOWN.");
+      Log.wtf(TAG, "onActivityLaunchFinished during UNKNOWN.");
       decAccIntentStartedEvents();
       return;
     }
 
     if (state != State.ACTIVITY_LAUNCHED) {
-      Log.e(TAG,
+      Log.wtf(TAG,
           String.format("Cannot transition from %s to %s", state, State.ACTIVITY_FINISHED));
       incAccIntentStartedEvents();
       return;
@@ -199,7 +199,7 @@
   public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity,
       long timestampNs) {
     if (state == State.UNKNOWN) {
-      Log.e(TAG, "onReportFullyDrawn during UNKNOWN.");
+      Log.wtf(TAG, "onReportFullyDrawn during UNKNOWN.");
       return;
     }
     if (state == State.INIT) {
@@ -207,7 +207,7 @@
     }
 
     if (state != State.ACTIVITY_FINISHED) {
-      Log.e(TAG,
+      Log.wtf(TAG,
           String.format("Cannot transition from %s to %s", state, State.REPORT_FULLY_DRAWN));
       return;
     }
@@ -230,7 +230,7 @@
   private void incAccIntentStartedEvents() {
     if (accIntentStartedEvents < 0) {
       throw new AssertionError(
-          String.format("The number of unknows cannot be negative"));
+          String.format("The number of unknowns cannot be negative"));
     }
     if (accIntentStartedEvents == 0) {
       state = State.UNKNOWN;
@@ -243,7 +243,7 @@
   private void decAccIntentStartedEvents() {
     if (accIntentStartedEvents <= 0) {
       throw new AssertionError(
-          String.format("The number of unknows cannot be negative"));
+          String.format("The number of unknowns cannot be negative"));
     }
     if(accIntentStartedEvents == 1) {
       state = State.INIT;
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 2a6e8de..58a7ea0 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -14,4 +14,5 @@
 refuhoo@google.com
 paulye@google.com
 nazaninb@google.com
-sarahchin@google.com
\ No newline at end of file
+sarahchin@google.com
+dbright@google.com
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index ce32dce..5345469 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -191,6 +191,7 @@
      *
      * @hide
      */
+    @SystemApi
     public abstract @NonNull CellIdentity sanitizeLocationInfo();
 
     @Override
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index f31fafe..d28d750 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -155,7 +155,17 @@
      * @param ss signal strength from modem.
      */
     public CellSignalStrengthNr(android.hardware.radio.V1_4.NrSignalStrength ss) {
-        this(ss.csiRsrp, ss.csiRsrq, ss.csiSinr, ss.ssRsrp, ss.ssRsrq, ss.ssSinr);
+        this(flip(ss.csiRsrp), flip(ss.csiRsrq), ss.csiSinr, flip(ss.ssRsrp), flip(ss.ssRsrq),
+                ss.ssSinr);
+    }
+
+    /**
+     * Flip sign cell strength value when taking in the value from hal
+     * @param val cell strength value
+     * @return flipped value
+     */
+    private static int flip(int val) {
+        return val != CellInfo.UNAVAILABLE ? -val : val;
     }
 
     /**
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index aff1391..be85b30 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -360,6 +360,12 @@
      */
     public static final int OUTGOING_EMERGENCY_CALL_PLACED = 80;
 
+    /**
+     * Indicates that incoming call was rejected by the modem before the call went in ringing
+     */
+    public static final int INCOMING_AUTO_REJECTED = 81;
+
+
     //*********************************************************************************************
     // When adding a disconnect type:
     // 1) Update toString() with the newly added disconnect type.
@@ -536,6 +542,8 @@
             return "WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION";
         case OUTGOING_EMERGENCY_CALL_PLACED:
             return "OUTGOING_EMERGENCY_CALL_PLACED";
+            case INCOMING_AUTO_REJECTED:
+                return "INCOMING_AUTO_REJECTED";
         default:
             return "INVALID: " + cause;
         }
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 8139330c..5b12aed 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -545,7 +545,7 @@
      * @see #STATE_EMERGENCY_ONLY
      * @see #STATE_POWER_OFF
      *
-     * @return current data registration state {@link RegState}
+     * @return current data registration state
      *
      * @hide
      */
@@ -562,7 +562,7 @@
      * @see #STATE_EMERGENCY_ONLY
      * @see #STATE_POWER_OFF
      *
-     * @return current data registration state {@link RegState}
+     * @return current data registration state
      *
      * @hide
      */
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 4c8a81b..22eed6e 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1199,12 +1199,17 @@
     }
 
     /**
-     * Get the active SubscriptionInfo associated with the iccId
+     * Gets an active SubscriptionInfo {@link SubscriptionInfo} associated with the Sim IccId.
+     *
      * @param iccId the IccId of SIM card
      * @return SubscriptionInfo, maybe null if its not active
+     *
      * @hide
      */
-    public SubscriptionInfo getActiveSubscriptionInfoForIccIndex(String iccId) {
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @Nullable
+    @SystemApi
+    public SubscriptionInfo getActiveSubscriptionInfoForIcc(@NonNull String iccId) {
         if (VDBG) logd("[getActiveSubscriptionInfoForIccIndex]+ iccId=" + iccId);
         if (iccId == null) {
             logd("[getActiveSubscriptionInfoForIccIndex]- null iccid");
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index df8c26d..4a42b6f 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -107,6 +107,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RILConstants;
 import com.android.internal.telephony.SmsApplication;
+import com.android.telephony.Rlog;
 
 import dalvik.system.VMRuntime;
 
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 034fc22..dbfb6a2 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -1056,6 +1056,11 @@
     }
 
     /** @hide */
+    public boolean isEmergencyApn() {
+        return hasApnType(TYPE_EMERGENCY);
+    }
+
+    /** @hide */
     public boolean canHandleType(@ApnType int type) {
         if (!mCarrierEnabled) {
             return false;
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 998b39d..bc60d81 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -279,6 +279,14 @@
                                   "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS";
 
     /**
+     * CallDisconnectCause: Specify call disconnect cause. This extra should be a code
+     * corresponding to ImsReasonInfo and should only be populated in the case that the
+     * call has already been missed
+     */
+    public static final String EXTRA_CALL_DISCONNECT_CAUSE =
+                                 "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE";
+
+    /**
      * Extra key which the RIL can use to indicate the radio technology used for a call.
      * Valid values are:
      * {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE},
diff --git a/telephony/java/com/android/telephony/Rlog.java b/telephony/java/com/android/telephony/Rlog.java
new file mode 100644
index 0000000..9d6c930
--- /dev/null
+++ b/telephony/java/com/android/telephony/Rlog.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2020 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.telephony;
+
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.internal.telephony.util.TelephonyUtils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * A copy of {@link android.telephony.Rlog} to be used within the telephony mainline module.
+ *
+ * @hide
+ */
+public final class Rlog {
+
+    private static final boolean USER_BUILD = TelephonyUtils.IS_USER;
+
+    private Rlog() {
+    }
+
+    private static int log(int priority, String tag, String msg) {
+        return Log.logToRadioBuffer(priority, tag, msg);
+    }
+
+    public static int v(String tag, String msg) {
+        return log(Log.VERBOSE, tag, msg);
+    }
+
+    public static int v(String tag, String msg, Throwable tr) {
+        return log(Log.VERBOSE, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    public static int d(String tag, String msg) {
+        return log(Log.DEBUG, tag, msg);
+    }
+
+    public static int d(String tag, String msg, Throwable tr) {
+        return log(Log.DEBUG, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    public static int i(String tag, String msg) {
+        return log(Log.INFO, tag, msg);
+    }
+
+    public static int i(String tag, String msg, Throwable tr) {
+        return log(Log.INFO, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    public static int w(String tag, String msg) {
+        return log(Log.WARN, tag, msg);
+    }
+
+    public static int w(String tag, String msg, Throwable tr) {
+        return log(Log.WARN, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    public static int w(String tag, Throwable tr) {
+        return log(Log.WARN, tag, Log.getStackTraceString(tr));
+    }
+
+    public static int e(String tag, String msg) {
+        return log(Log.ERROR, tag, msg);
+    }
+
+    public static int e(String tag, String msg, Throwable tr) {
+        return log(Log.ERROR, tag,
+                msg + '\n' + Log.getStackTraceString(tr));
+    }
+
+    public static int println(int priority, String tag, String msg) {
+        return log(priority, tag, msg);
+    }
+
+    public static boolean isLoggable(String tag, int level) {
+        return Log.isLoggable(tag, level);
+    }
+
+    /**
+     * Redact personally identifiable information for production users.
+     * @param tag used to identify the source of a log message
+     * @param pii the personally identifiable information we want to apply secure hash on.
+     * @return If tag is loggable in verbose mode or pii is null, return the original input.
+     * otherwise return a secure Hash of input pii
+     */
+    public static String pii(String tag, Object pii) {
+        String val = String.valueOf(pii);
+        if (pii == null || TextUtils.isEmpty(val) || isLoggable(tag, Log.VERBOSE)) {
+            return val;
+        }
+        return "[" + secureHash(val.getBytes()) + "]";
+    }
+
+    /**
+     * Redact personally identifiable information for production users.
+     * @param enablePiiLogging set when caller explicitly want to enable sensitive logging.
+     * @param pii the personally identifiable information we want to apply secure hash on.
+     * @return If enablePiiLogging is set to true or pii is null, return the original input.
+     * otherwise return a secure Hash of input pii
+     */
+    public static String pii(boolean enablePiiLogging, Object pii) {
+        String val = String.valueOf(pii);
+        if (pii == null || TextUtils.isEmpty(val) || enablePiiLogging) {
+            return val;
+        }
+        return "[" + secureHash(val.getBytes()) + "]";
+    }
+
+    /**
+     * Returns a secure hash (using the SHA1 algorithm) of the provided input.
+     *
+     * @return "****" if the build type is user, otherwise the hash
+     * @param input the bytes for which the secure hash should be computed.
+     */
+    private static String secureHash(byte[] input) {
+        // Refrain from logging user personal information in user build.
+        if (USER_BUILD) {
+            return "****";
+        }
+
+        MessageDigest messageDigest;
+
+        try {
+            messageDigest = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException e) {
+            return "####";
+        }
+
+        byte[] result = messageDigest.digest(input);
+        return Base64.encodeToString(
+                result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
+    }
+}
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index c557656..74e2a098 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -1641,8 +1641,14 @@
                                            ParsedResource* out_resource) {
   out_resource->name.type = ResourceType::kStyleable;
 
-  // Declare-styleable is kPrivate by default, because it technically only exists in R.java.
-  out_resource->visibility_level = Visibility::Level::kPublic;
+  if (!options_.preserve_visibility_of_styleables) {
+    // This was added in change Idd21b5de4d20be06c6f8c8eb5a22ccd68afc4927 to mimic aapt1, but no one
+    // knows exactly what for.
+    //
+    // FWIW, styleables only appear in generated R classes.  For custom views these should always be
+    // package-private (to be used only by the view class); themes are a different story.
+    out_resource->visibility_level = Visibility::Level::kPublic;
+  }
 
   // Declare-styleable only ends up in default config;
   if (out_resource->config != ConfigDescription::DefaultConfig()) {
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 06bb0c9..9d3ecc8 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -46,6 +46,12 @@
    */
   bool error_on_positional_arguments = true;
 
+  /**
+   * If true, apply the same visibility rules for styleables as are used for
+   * all other resources.  Otherwise, all styleables will be made public.
+   */
+  bool preserve_visibility_of_styleables = false;
+
   // If visibility was forced, we need to use it when creating a new resource and also error if we
   // try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags.
   Maybe<Visibility::Level> visibility;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 4237469..24531bc 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -614,6 +614,32 @@
   EXPECT_THAT(styleable->entries[2].name, Eq(make_value(test::ParseNameOrDie("attr/baz"))));
 }
 
+TEST_F(ResourceParserTest, ParseDeclareStyleablePreservingVisibility) {
+  StringInputStream input(R"(
+      <resources>
+        <declare-styleable name="foo">
+          <attr name="myattr" />
+        </declare-styleable>
+        <declare-styleable name="bar">
+          <attr name="myattr" />
+        </declare-styleable>
+        <public type="styleable" name="bar" />
+      </resources>)");
+  ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"},
+                        ConfigDescription::DefaultConfig(),
+                        ResourceParserOptions{.preserve_visibility_of_styleables = true});
+
+  xml::XmlPullParser xml_parser(&input);
+  ASSERT_TRUE(parser.Parse(&xml_parser));
+
+  EXPECT_EQ(
+      table_.FindResource(test::ParseNameOrDie("styleable/foo")).value().entry->visibility.level,
+      Visibility::Level::kUndefined);
+  EXPECT_EQ(
+      table_.FindResource(test::ParseNameOrDie("styleable/bar")).value().entry->visibility.level,
+      Visibility::Level::kPublic);
+}
+
 TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) {
   std::string input = R"(
       <declare-styleable xmlns:privAndroid="http://schemas.android.com/apk/prv/res/android"
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index d50b1de..3268653 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -159,6 +159,7 @@
 
     ResourceParserOptions parser_options;
     parser_options.error_on_positional_arguments = !options.legacy_mode;
+    parser_options.preserve_visibility_of_styleables = options.preserve_visibility_of_styleables;
     parser_options.translatable = translatable_file;
 
     // If visibility was forced, we need to use it when creating a new resource and also error if
diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h
index d3456b2..1752a1a 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -35,6 +35,8 @@
   bool pseudolocalize = false;
   bool no_png_crunch = false;
   bool legacy_mode = false;
+  // See comments on aapt::ResourceParserOptions.
+  bool preserve_visibility_of_styleables = false;
   bool verbose = false;
 };
 
@@ -56,6 +58,11 @@
     AddOptionalSwitch("--no-crunch", "Disables PNG processing", &options_.no_png_crunch);
     AddOptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
         &options_.legacy_mode);
+    AddOptionalSwitch("--preserve-visibility-of-styleables",
+                      "If specified, apply the same visibility rules for\n"
+                      "styleables as are used for all other resources.\n"
+                      "Otherwise, all stylesables will be made public.",
+                      &options_.preserve_visibility_of_styleables);
     AddOptionalFlag("--visibility",
         "Sets the visibility of the compiled resources to the specified\n"
             "level. Accepted levels: public, private, default", &visibility_);
diff --git a/wifi/Android.bp b/wifi/Android.bp
index 3934af1..09d5386 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -48,6 +48,7 @@
     "//frameworks/opt/net/wifi/tests/wifitests:__subpackages__",
 
     "//frameworks/opt/net/wifi/libs/WifiTrackerLib/tests",
+    "//external/robolectric-shadows:__subpackages__",
 ]
 
 java_library {
@@ -105,8 +106,10 @@
     name: "framework-wifi-test-defaults",
     sdk_version: "core_platform", // tests can use @CorePlatformApi's
     libs: [
+        // order matters: classes in framework-wifi are resolved before framework, meaning
+        // @hide APIs in framework-wifi are resolved before @SystemApi stubs in framework
         "framework-wifi",
-        "framework-minus-apex",
+        "framework",
 
         // if sdk_version="" this gets automatically included, but here we need to add manually.
         "framework-res",
diff --git a/wifi/java/android/net/wifi/SoftApCapability.java b/wifi/java/android/net/wifi/SoftApCapability.java
index c4474e2..2bbe7d2 100644
--- a/wifi/java/android/net/wifi/SoftApCapability.java
+++ b/wifi/java/android/net/wifi/SoftApCapability.java
@@ -61,11 +61,20 @@
      */
     public static final int SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT = 1 << 1;
 
+
+    /**
+     * Support for WPA3 Simultaneous Authentication of Equals (WPA3-SAE).
+     *
+     * flag when {@link config_wifi_softap_sae_supported)} is true.
+     */
+    public static final int SOFTAP_FEATURE_WPA3_SAE = 1 << 2;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, prefix = { "SOFTAP_FEATURE_" }, value = {
             SOFTAP_FEATURE_ACS_OFFLOAD,
             SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT,
+            SOFTAP_FEATURE_WPA3_SAE,
     })
     public @interface HotspotFeatures {}
 
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index 05e245b..65e9b79 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -25,6 +25,7 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import java.lang.annotation.Retention;
@@ -55,6 +56,11 @@
 @SystemApi
 public final class SoftApConfiguration implements Parcelable {
 
+    @VisibleForTesting
+    static final int PSK_MIN_LEN = 8;
+    @VisibleForTesting
+    static final int PSK_MAX_LEN = 63;
+
     /**
      * 2GHz band.
      * @hide
@@ -142,9 +148,10 @@
     private final @Nullable MacAddress mBssid;
 
     /**
-     * Pre-shared key for WPA2-PSK encryption (non-null enables WPA2-PSK).
+     * Pre-shared key for WPA2-PSK or WPA3-SAE-Transition or WPA3-SAE encryption which depends on
+     * the security type.
      */
-    private final @Nullable String mWpa2Passphrase;
+    private final @Nullable String mPassphrase;
 
     /**
      * This is a network that does not broadcast its SSID, so an
@@ -186,20 +193,30 @@
     public static final int SECURITY_TYPE_WPA2_PSK = 1;
 
     /** @hide */
+    @SystemApi
+    public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2;
+
+    /** @hide */
+    @SystemApi
+    public static final int SECURITY_TYPE_WPA3_SAE = 3;
+
+    /** @hide */
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(prefix = { "SECURITY_TYPE" }, value = {
+    @IntDef(prefix = { "SECURITY_TYPE_" }, value = {
         SECURITY_TYPE_OPEN,
         SECURITY_TYPE_WPA2_PSK,
+        SECURITY_TYPE_WPA3_SAE_TRANSITION,
+        SECURITY_TYPE_WPA3_SAE,
     })
     public @interface SecurityType {}
 
     /** Private constructor for Builder and Parcelable implementation. */
     private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid,
-            @Nullable String wpa2Passphrase, boolean hiddenSsid, @BandType int band, int channel,
+            @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel,
             @SecurityType int securityType, int maxNumberOfClients) {
         mSsid = ssid;
         mBssid = bssid;
-        mWpa2Passphrase = wpa2Passphrase;
+        mPassphrase = passphrase;
         mHiddenSsid = hiddenSsid;
         mBand = band;
         mChannel = channel;
@@ -218,7 +235,7 @@
         SoftApConfiguration other = (SoftApConfiguration) otherObj;
         return Objects.equals(mSsid, other.mSsid)
                 && Objects.equals(mBssid, other.mBssid)
-                && Objects.equals(mWpa2Passphrase, other.mWpa2Passphrase)
+                && Objects.equals(mPassphrase, other.mPassphrase)
                 && mHiddenSsid == other.mHiddenSsid
                 && mBand == other.mBand
                 && mChannel == other.mChannel
@@ -228,7 +245,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mSsid, mBssid, mWpa2Passphrase, mHiddenSsid,
+        return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid,
                 mBand, mChannel, mSecurityType, mMaxNumberOfClients);
     }
 
@@ -237,8 +254,8 @@
         StringBuilder sbuf = new StringBuilder();
         sbuf.append("ssid=").append(mSsid);
         if (mBssid != null) sbuf.append(" \n bssid=").append(mBssid.toString());
-        sbuf.append(" \n Wpa2Passphrase =").append(
-                TextUtils.isEmpty(mWpa2Passphrase) ? "<empty>" : "<non-empty>");
+        sbuf.append(" \n Passphrase =").append(
+                TextUtils.isEmpty(mPassphrase) ? "<empty>" : "<non-empty>");
         sbuf.append(" \n HiddenSsid =").append(mHiddenSsid);
         sbuf.append(" \n Band =").append(mBand);
         sbuf.append(" \n Channel =").append(mChannel);
@@ -251,7 +268,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mSsid);
         dest.writeParcelable(mBssid, flags);
-        dest.writeString(mWpa2Passphrase);
+        dest.writeString(mPassphrase);
         dest.writeBoolean(mHiddenSsid);
         dest.writeInt(mBand);
         dest.writeInt(mChannel);
@@ -299,13 +316,26 @@
         return mBssid;
     }
 
+    // TODO: Remove it after update the caller
     /**
      * Returns String set to be passphrase for the WPA2-PSK AP.
-     * {@link Builder#setWpa2Passphrase(String)}.
+     * {@link #setWpa2Passphrase(String)}.
      */
     @Nullable
     public String getWpa2Passphrase() {
-        return mWpa2Passphrase;
+        if (mSecurityType == SECURITY_TYPE_WPA2_PSK) {
+            return mPassphrase;
+        }
+        return null;
+    }
+
+    /**
+     * Returns String set to be passphrase for current AP.
+     * {@link #setPassphrase(String, @SecurityType int)}.
+     */
+    @Nullable
+    public String getPassphrase() {
+        return mPassphrase;
     }
 
     /**
@@ -360,23 +390,12 @@
     public static final class Builder {
         private String mSsid;
         private MacAddress mBssid;
-        private String mWpa2Passphrase;
+        private String mPassphrase;
         private boolean mHiddenSsid;
         private int mBand;
         private int mChannel;
         private int mMaxNumberOfClients;
-
-        private int setSecurityType() {
-            int securityType = SECURITY_TYPE_OPEN;
-            if (!TextUtils.isEmpty(mWpa2Passphrase)) { // WPA2-PSK network.
-                securityType = SECURITY_TYPE_WPA2_PSK;
-            }
-            return securityType;
-        }
-
-        private void clearAllPassphrase() {
-            mWpa2Passphrase = null;
-        }
+        private int mSecurityType;
 
         /**
          * Constructs a Builder with default values (see {@link Builder}).
@@ -384,11 +403,12 @@
         public Builder() {
             mSsid = null;
             mBssid = null;
-            mWpa2Passphrase = null;
+            mPassphrase = null;
             mHiddenSsid = false;
             mBand = BAND_2GHZ;
             mChannel = 0;
             mMaxNumberOfClients = 0;
+            mSecurityType = SECURITY_TYPE_OPEN;
         }
 
         /**
@@ -399,11 +419,12 @@
 
             mSsid = other.mSsid;
             mBssid = other.mBssid;
-            mWpa2Passphrase = other.mWpa2Passphrase;
+            mPassphrase = other.mPassphrase;
             mHiddenSsid = other.mHiddenSsid;
             mBand = other.mBand;
             mChannel = other.mChannel;
             mMaxNumberOfClients = other.mMaxNumberOfClients;
+            mSecurityType = other.mSecurityType;
         }
 
         /**
@@ -413,8 +434,8 @@
          */
         @NonNull
         public SoftApConfiguration build() {
-            return new SoftApConfiguration(mSsid, mBssid, mWpa2Passphrase,
-                mHiddenSsid, mBand, mChannel, setSecurityType(), mMaxNumberOfClients);
+            return new SoftApConfiguration(mSsid, mBssid, mPassphrase,
+                mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients);
         }
 
         /**
@@ -461,6 +482,7 @@
             return this;
         }
 
+        // TODO: Remove it after update the caller
         /**
          * Specifies that this AP should use WPA2-PSK with the given ASCII WPA2 passphrase.
          * When set to null, an open network is created.
@@ -473,15 +495,47 @@
          */
         @NonNull
         public Builder setWpa2Passphrase(@Nullable String passphrase) {
-            if (passphrase != null) {
+            return setPassphrase(passphrase, SECURITY_TYPE_WPA2_PSK);
+        }
+
+        /**
+         * Specifies that this AP should use specific security type with the given ASCII passphrase.
+         *
+         * @param securityType one of the security types from {@link @SecurityType}.
+         * @param passphrase The passphrase to use for sepcific {@link @SecurityType} configuration
+         * or null with {@link @SecurityType#SECURITY_TYPE_OPEN}.
+         *
+         * @return Builder for chaining.
+         * @throws IllegalArgumentException when the passphrase length is invalid and
+         *         {@code securityType} is not {@link @SecurityType#SECURITY_TYPE_OPEN}
+         *         or non-null passphrase and {@code securityType} is
+         *         {@link @SecurityType#SECURITY_TYPE_OPEN}.
+         */
+        @NonNull
+        public Builder setPassphrase(@Nullable String passphrase, @SecurityType int securityType) {
+            if (securityType == SECURITY_TYPE_OPEN) {
+                if (passphrase != null) {
+                    throw new IllegalArgumentException(
+                            "passphrase should be null when security type is open");
+                }
+            } else {
+                Preconditions.checkStringNotEmpty(passphrase);
                 final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
                 if (!asciiEncoder.canEncode(passphrase)) {
                     throw new IllegalArgumentException("passphrase not ASCII encodable");
                 }
-                Preconditions.checkStringNotEmpty(passphrase);
+                if (securityType == SECURITY_TYPE_WPA2_PSK
+                        || securityType == SECURITY_TYPE_WPA3_SAE_TRANSITION) {
+                    if (passphrase.length() < PSK_MIN_LEN || passphrase.length() > PSK_MAX_LEN) {
+                        throw new IllegalArgumentException(
+                                "Password size must be at least " + PSK_MIN_LEN
+                                + " and no more than " + PSK_MAX_LEN
+                                + " for WPA2_PSK and WPA3_SAE_TRANSITION Mode");
+                    }
+                }
             }
-            clearAllPassphrase();
-            mWpa2Passphrase = passphrase;
+            mSecurityType = securityType;
+            mPassphrase = passphrase;
             return this;
         }
 
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 148e8a4..41f7c6e 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -1336,20 +1336,18 @@
     }
 
     /**
-     * If the current authentication method needs SIM card.
-     * @return true if the credential information require SIM card for current authentication
+     * Utility method to determine whether the configuration's authentication method is SIM-based.
+     *
+     * @return true if the credential information requires SIM card for current authentication
      * method, otherwise it returns false.
-     * @hide
      */
-    public boolean requireSimCredential() {
+    public boolean isAuthenticationSimBased() {
         if (mEapMethod == Eap.SIM || mEapMethod == Eap.AKA || mEapMethod == Eap.AKA_PRIME) {
             return true;
         }
         if (mEapMethod == Eap.PEAP) {
-            if (mPhase2Method == Phase2.SIM || mPhase2Method == Phase2.AKA
-                    || mPhase2Method == Phase2.AKA_PRIME) {
-                return true;
-            }
+            return mPhase2Method == Phase2.SIM || mPhase2Method == Phase2.AKA
+                    || mPhase2Method == Phase2.AKA_PRIME;
         }
         return false;
     }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 378c67b..c4ec44e 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -545,15 +545,22 @@
     public static final String EXTRA_WIFI_AP_STATE = "wifi_state";
 
     /**
-     * The look up key for an int that indicates why softAP started failed
-     * currently support general and no_channel
-     * @see #SAP_START_FAILURE_GENERAL
-     * @see #SAP_START_FAILURE_NO_CHANNEL
-     * @see #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION
+     * An extra containing the int error code for Soft AP start failure.
+     * Can be obtained from the {@link #WIFI_AP_STATE_CHANGED_ACTION} using
+     * {@link android.content.Intent#getIntExtra}.
+     * This extra will only be attached if {@link #EXTRA_WIFI_AP_STATE} is
+     * attached and is equal to {@link #WIFI_AP_STATE_FAILED}.
+     *
+     * The error code will be one of:
+     * {@link #SAP_START_FAILURE_GENERAL},
+     * {@link #SAP_START_FAILURE_NO_CHANNEL},
+     * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}
      *
      * @hide
      */
-    public static final String EXTRA_WIFI_AP_FAILURE_REASON = "wifi_ap_error_code";
+    @SystemApi
+    public static final String EXTRA_WIFI_AP_FAILURE_REASON =
+            "android.net.wifi.extra.WIFI_AP_FAILURE_REASON";
     /**
      * The previous Wi-Fi state.
      *
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index 1f60103..acd3343 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -25,8 +25,12 @@
 
 import org.junit.Test;
 
+import java.util.Random;
+
 @SmallTest
 public class SoftApConfigurationTest {
+    private static final String TEST_CHAR_SET_AS_STRING = "abcdefghijklmnopqrstuvwxyz0123456789";
+
     private SoftApConfiguration parcelUnparcel(SoftApConfiguration configIn) {
         Parcel parcel = Parcel.obtain();
         parcel.writeParcelable(configIn, 0);
@@ -37,6 +41,25 @@
         return configOut;
     }
 
+    /**
+     * Helper method to generate random string.
+     *
+     * Note: this method has limited use as a random string generator.
+     * The characters used in this method do no not cover all valid inputs.
+     * @param length number of characters to generate for the string
+     * @return String generated string of random characters
+     */
+    private String generateRandomString(int length) {
+        Random random = new Random();
+        StringBuilder stringBuilder = new StringBuilder(length);
+        int index = -1;
+        while (stringBuilder.length() < length) {
+            index = random.nextInt(TEST_CHAR_SET_AS_STRING.length());
+            stringBuilder.append(TEST_CHAR_SET_AS_STRING.charAt(index));
+        }
+        return stringBuilder.toString();
+    }
+
     @Test
     public void testBasicSettings() {
         SoftApConfiguration original = new SoftApConfiguration.Builder()
@@ -45,7 +68,7 @@
                 .build();
         assertThat(original.getSsid()).isEqualTo("ssid");
         assertThat(original.getBssid()).isEqualTo(MacAddress.fromString("11:22:33:44:55:66"));
-        assertThat(original.getWpa2Passphrase()).isNull();
+        assertThat(original.getPassphrase()).isNull();
         assertThat(original.getSecurityType()).isEqualTo(SoftApConfiguration.SECURITY_TYPE_OPEN);
         assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ);
         assertThat(original.getChannel()).isEqualTo(0);
@@ -66,9 +89,9 @@
     @Test
     public void testWpa2() {
         SoftApConfiguration original = new SoftApConfiguration.Builder()
-                .setWpa2Passphrase("secretsecret")
+                .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .build();
-        assertThat(original.getWpa2Passphrase()).isEqualTo("secretsecret");
+        assertThat(original.getPassphrase()).isEqualTo("secretsecret");
         assertThat(original.getSecurityType()).isEqualTo(
                 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
         assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ);
@@ -90,13 +113,12 @@
     @Test
     public void testWpa2WithAllFieldCustomized() {
         SoftApConfiguration original = new SoftApConfiguration.Builder()
-                .setWpa2Passphrase("secretsecret")
-                .setBand(SoftApConfiguration.BAND_ANY)
+                .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .setChannel(149, SoftApConfiguration.BAND_5GHZ)
                 .setHiddenSsid(true)
                 .setMaxNumberOfClients(10)
                 .build();
-        assertThat(original.getWpa2Passphrase()).isEqualTo("secretsecret");
+        assertThat(original.getPassphrase()).isEqualTo("secretsecret");
         assertThat(original.getSecurityType()).isEqualTo(
                 SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
         assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
@@ -114,4 +136,98 @@
         assertThat(copy).isEqualTo(original);
         assertThat(copy.hashCode()).isEqualTo(original.hashCode());
     }
+
+    @Test
+    public void testWpa3Sae() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA3_SAE)
+                .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+                .setHiddenSsid(true)
+                .build();
+        assertThat(original.getPassphrase()).isEqualTo("secretsecret");
+        assertThat(original.getSecurityType()).isEqualTo(
+                SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
+        assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
+        assertThat(original.getChannel()).isEqualTo(149);
+        assertThat(original.isHiddenSsid()).isEqualTo(true);
+
+
+        SoftApConfiguration unparceled = parcelUnparcel(original);
+        assertThat(unparceled).isNotSameAs(original);
+        assertThat(unparceled).isEqualTo(original);
+        assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
+
+        SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
+        assertThat(copy).isNotSameAs(original);
+        assertThat(copy).isEqualTo(original);
+        assertThat(copy.hashCode()).isEqualTo(original.hashCode());
+    }
+
+    @Test
+    public void testWpa3SaeTransition() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setPassphrase("secretsecret",
+                        SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+                .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+                .setHiddenSsid(true)
+                .build();
+        assertThat(original.getSecurityType()).isEqualTo(
+                SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION);
+        assertThat(original.getPassphrase()).isEqualTo("secretsecret");
+        assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
+        assertThat(original.getChannel()).isEqualTo(149);
+        assertThat(original.isHiddenSsid()).isEqualTo(true);
+
+
+        SoftApConfiguration unparceled = parcelUnparcel(original);
+        assertThat(unparceled).isNotSameAs(original);
+        assertThat(unparceled).isEqualTo(original);
+        assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
+
+        SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
+        assertThat(copy).isNotSameAs(original);
+        assertThat(copy).isEqualTo(original);
+        assertThat(copy.hashCode()).isEqualTo(original.hashCode());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidShortPasswordLengthForWpa2() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MIN_LEN - 1),
+                        SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+                .setHiddenSsid(true)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidLongPasswordLengthForWpa2() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MAX_LEN + 1),
+                        SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+                .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+                .setHiddenSsid(true)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidShortPasswordLengthForWpa3SaeTransition() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MIN_LEN - 1),
+                        SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+                .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+                .setHiddenSsid(true)
+                .build();
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidLongPasswordLengthForWpa3SaeTransition() {
+        SoftApConfiguration original = new SoftApConfiguration.Builder()
+                .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MAX_LEN + 1),
+                        SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+                .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+                .setHiddenSsid(true)
+                .build();
+    }
+
 }