Merge "Comment out Statsd tests"
diff --git a/apex/blobstore/TEST_MAPPING b/apex/blobstore/TEST_MAPPING
index 25a1537..6d3c0d7 100644
--- a/apex/blobstore/TEST_MAPPING
+++ b/apex/blobstore/TEST_MAPPING
@@ -4,6 +4,9 @@
       "name": "CtsBlobStoreTestCases"
     },
     {
+      "name": "CtsBlobStoreHostTestCases"
+    },
+    {
       "name": "FrameworksMockingServicesTests",
       "options": [
         {
@@ -12,4 +15,4 @@
       ]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
index 80b4235..d33a09f 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -514,7 +514,7 @@
     static BlobStoreSession createFromXml(@NonNull XmlPullParser in, int version,
             @NonNull Context context, @NonNull SessionStateChangeListener stateChangeListener)
             throws IOException, XmlPullParserException {
-        final int sessionId = XmlUtils.readIntAttribute(in, ATTR_ID);
+        final long sessionId = XmlUtils.readLongAttribute(in, ATTR_ID);
         final String ownerPackageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE);
         final int ownerUid = XmlUtils.readIntAttribute(in, ATTR_UID);
 
diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp
index c8aa526..0e93110 100644
--- a/apex/statsd/Android.bp
+++ b/apex/statsd/Android.bp
@@ -27,6 +27,7 @@
         "framework-statsd",
         "service-statsd",
     ],
+    compile_multilib: "both",
     // prebuilts: ["my_prebuilt"],
     name: "com.android.os.statsd-defaults",
     key: "com.android.os.statsd.key",
@@ -72,4 +73,4 @@
         "com.android.os.statsd",
         "test_com.android.os.statsd",
     ],
-}
\ No newline at end of file
+}
diff --git a/api/current.txt b/api/current.txt
index 7c80f5e..76dad98 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4351,7 +4351,7 @@
   public class AppOpsManager {
     method @Deprecated public int checkOp(@NonNull String, int, @NonNull String);
     method @Deprecated public int checkOpNoThrow(@NonNull String, int, @NonNull String);
-    method public void checkPackage(int, @NonNull String);
+    method @Deprecated public void checkPackage(int, @NonNull String);
     method @Deprecated public void finishOp(@NonNull String, int, @NonNull String);
     method public void finishOp(@NonNull String, int, @NonNull String, @Nullable String);
     method public boolean isOpActive(@NonNull String, int, @NonNull String);
@@ -4364,7 +4364,7 @@
     method @Deprecated public int noteProxyOpNoThrow(@NonNull String, @NonNull String);
     method @Deprecated public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int);
     method public int noteProxyOpNoThrow(@NonNull String, @Nullable String, int, @Nullable String, @Nullable String);
-    method public static String permissionToOp(String);
+    method @Nullable public static String permissionToOp(@NonNull String);
     method public void setNotedAppOpsCollector(@Nullable android.app.AppOpsManager.AppOpsCollector);
     method @Deprecated public int startOp(@NonNull String, int, @NonNull String);
     method public int startOp(@NonNull String, int, @Nullable String, @Nullable String, @Nullable String);
@@ -10827,6 +10827,8 @@
     field public static final int FLAG_ACTIVITY_NO_USER_ACTION = 262144; // 0x40000
     field public static final int FLAG_ACTIVITY_PREVIOUS_IS_TOP = 16777216; // 0x1000000
     field public static final int FLAG_ACTIVITY_REORDER_TO_FRONT = 131072; // 0x20000
+    field public static final int FLAG_ACTIVITY_REQUIRE_DEFAULT = 512; // 0x200
+    field public static final int FLAG_ACTIVITY_REQUIRE_NON_BROWSER = 1024; // 0x400
     field public static final int FLAG_ACTIVITY_RESET_TASK_IF_NEEDED = 2097152; // 0x200000
     field public static final int FLAG_ACTIVITY_RETAIN_IN_RECENTS = 8192; // 0x2000
     field public static final int FLAG_ACTIVITY_SINGLE_TOP = 536870912; // 0x20000000
@@ -25092,7 +25094,7 @@
 
   public final class MediaCas implements java.lang.AutoCloseable {
     ctor public MediaCas(int) throws android.media.MediaCasException.UnsupportedCasException;
-    ctor public MediaCas(int, @Nullable String, int) throws android.media.MediaCasException.UnsupportedCasException;
+    ctor public MediaCas(@NonNull android.content.Context, int, @Nullable String, int) throws android.media.MediaCasException.UnsupportedCasException;
     method public void close();
     method public static android.media.MediaCas.PluginDescriptor[] enumeratePlugins();
     method protected void finalize();
diff --git a/api/system-current.txt b/api/system-current.txt
index a8c2453..3d14961 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -371,8 +371,8 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS) public java.util.List<android.app.AppOpsManager.PackageOps> getPackagesForOps(@Nullable String[]);
     method public static int opToDefaultMode(@NonNull String);
     method @Nullable public static String opToPermission(@NonNull String);
-    method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(String, int, String, int);
-    method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(String, int, int);
+    method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(@NonNull String, int, @Nullable String, int);
+    method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(@NonNull String, int, int);
     field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
     field public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility";
     field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
@@ -4826,7 +4826,7 @@
     method public int getAudioFilterCount();
     method public int getDemuxCount();
     method public int getFilterCapabilities();
-    method @Nullable @Size(5) public int[] getLinkCapabilities();
+    method @NonNull @Size(5) public int[] getLinkCapabilities();
     method public int getPcrFilterCount();
     method public int getPesFilterCount();
     method public int getPlaybackCount();
@@ -4842,7 +4842,7 @@
     method public int addPid(int, int, @Nullable android.media.tv.tuner.filter.Filter);
     method public void close();
     method public int removePid(int, int, @Nullable android.media.tv.tuner.filter.Filter);
-    method public int setKeyToken(@Nullable byte[]);
+    method public int setKeyToken(@NonNull byte[]);
     field public static final int PID_TYPE_MMTP = 2; // 0x2
     field public static final int PID_TYPE_T = 1; // 0x1
   }
@@ -4879,10 +4879,11 @@
   }
 
   public class Tuner implements java.lang.AutoCloseable {
-    ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @NonNull String, int, @Nullable android.media.tv.tuner.Tuner.OnResourceLostListener);
+    ctor @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public Tuner(@NonNull android.content.Context, @Nullable String, int);
     method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int cancelScanning();
     method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int cancelTuning();
     method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void clearOnTuneEventListener();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void clearResourceLostListener();
     method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close();
     method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int connectCiCam(int);
     method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int disconnectCiCam();
@@ -4892,15 +4893,16 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.frontend.FrontendInfo getFrontendInfo();
     method @Nullable public android.media.tv.tuner.frontend.FrontendStatus getFrontendStatus(@NonNull int[]);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_DESCRAMBLER) public android.media.tv.tuner.Descrambler openDescrambler();
-    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
-    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.dvr.DvrRecorder openDvrRecorder(long, @Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.dvr.OnRecordStatusChangedListener);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.dvr.DvrPlayback openDvrPlayback(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnPlaybackStatusChangedListener);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.dvr.DvrRecorder openDvrRecorder(long, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.dvr.OnRecordStatusChangedListener);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.filter.Filter openFilter(int, int, long, @Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.filter.FilterCallback);
-    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Lnb openLnb(@Nullable java.util.concurrent.Executor, @Nullable android.media.tv.tuner.LnbCallback);
-    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Lnb openLnbByName(@Nullable String, @Nullable java.util.concurrent.Executor, @NonNull android.media.tv.tuner.LnbCallback);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Lnb openLnb(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.LnbCallback);
+    method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Lnb openLnbByName(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.LnbCallback);
     method @Nullable public android.media.tv.tuner.filter.TimeFilter openTimeFilter();
     method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int scan(@NonNull android.media.tv.tuner.frontend.FrontendSettings, int, @NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.ScanCallback);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setLna(boolean);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setLnaEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void setOnTuneEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.frontend.OnTuneEventListener);
+    method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void setResourceLostListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.tv.tuner.Tuner.OnResourceLostListener);
     method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void shareFrontendFromTuner(@NonNull android.media.tv.tuner.Tuner);
     method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int tune(@NonNull android.media.tv.tuner.frontend.FrontendSettings);
     method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void updateResourcePriority(int, int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 79d29f6..957794c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -180,8 +180,8 @@
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void resetHistoryParameters();
     method @RequiresPermission("android.permission.MANAGE_APPOPS") public void setHistoryParameters(int, long, int);
     method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(int, int, String, int);
-    method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(String, int, String, int);
-    method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(String, int, int);
+    method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(@NonNull String, int, @Nullable String, int);
+    method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(@NonNull String, int, int);
     method public static int strOpToOp(@NonNull String);
     field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0
     field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 41a1706..66f5c39 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -146,6 +146,7 @@
     host_supported: true,
     srcs: [
         "idmap2/Create.cpp",
+        "idmap2/CreateMultiple.cpp",
         "idmap2/Dump.cpp",
         "idmap2/Lookup.cpp",
         "idmap2/Main.cpp",
diff --git a/cmds/idmap2/idmap2/Commands.h b/cmds/idmap2/idmap2/Commands.h
index 718e361..e626738 100644
--- a/cmds/idmap2/idmap2/Commands.h
+++ b/cmds/idmap2/idmap2/Commands.h
@@ -23,6 +23,7 @@
 #include "idmap2/Result.h"
 
 android::idmap2::Result<android::idmap2::Unit> Create(const std::vector<std::string>& args);
+android::idmap2::Result<android::idmap2::Unit> CreateMultiple(const std::vector<std::string>& args);
 android::idmap2::Result<android::idmap2::Unit> Dump(const std::vector<std::string>& args);
 android::idmap2::Result<android::idmap2::Unit> Lookup(const std::vector<std::string>& args);
 android::idmap2::Result<android::idmap2::Unit> Scan(const std::vector<std::string>& args);
diff --git a/cmds/idmap2/idmap2/CreateMultiple.cpp b/cmds/idmap2/idmap2/CreateMultiple.cpp
new file mode 100644
index 0000000..0b0541f
--- /dev/null
+++ b/cmds/idmap2/idmap2/CreateMultiple.cpp
@@ -0,0 +1,144 @@
+/*
+ * 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.
+ */
+
+#include <sys/stat.h>   // umask
+#include <sys/types.h>  // umask
+
+#include <fstream>
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "android-base/stringprintf.h"
+#include "idmap2/BinaryStreamVisitor.h"
+#include "idmap2/CommandLineOptions.h"
+#include "idmap2/FileUtils.h"
+#include "idmap2/Idmap.h"
+#include "idmap2/Policies.h"
+#include "idmap2/SysTrace.h"
+
+using android::ApkAssets;
+using android::base::StringPrintf;
+using android::idmap2::BinaryStreamVisitor;
+using android::idmap2::CommandLineOptions;
+using android::idmap2::Error;
+using android::idmap2::Idmap;
+using android::idmap2::PoliciesToBitmask;
+using android::idmap2::PolicyBitmask;
+using android::idmap2::PolicyFlags;
+using android::idmap2::Result;
+using android::idmap2::Unit;
+using android::idmap2::utils::kIdmapCacheDir;
+using android::idmap2::utils::kIdmapFilePermissionMask;
+using android::idmap2::utils::UidHasWriteAccessToPath;
+
+Result<Unit> CreateMultiple(const std::vector<std::string>& args) {
+  SYSTRACE << "CreateMultiple " << args;
+  std::string target_apk_path;
+  std::string idmap_dir = kIdmapCacheDir;
+  std::vector<std::string> overlay_apk_paths;
+  std::vector<std::string> policies;
+  bool ignore_overlayable = false;
+
+  const CommandLineOptions opts =
+      CommandLineOptions("idmap2 create-multiple")
+          .MandatoryOption("--target-apk-path",
+                           "input: path to apk which will have its resources overlaid",
+                           &target_apk_path)
+          .MandatoryOption("--overlay-apk-path",
+                           "input: path to apk which contains the new resource values",
+                           &overlay_apk_paths)
+          .OptionalOption("--idmap-dir",
+                          StringPrintf("output: path to the directory in which to write idmap file"
+                                       " (defaults to %s)",
+                                       kIdmapCacheDir),
+                          &idmap_dir)
+          .OptionalOption("--policy",
+                          "input: an overlayable policy this overlay fulfills"
+                          " (if none or supplied, the overlay policy will default to \"public\")",
+                          &policies)
+          .OptionalFlag("--ignore-overlayable", "disables overlayable and policy checks",
+                        &ignore_overlayable);
+  const auto opts_ok = opts.Parse(args);
+  if (!opts_ok) {
+    return opts_ok.GetError();
+  }
+
+  PolicyBitmask fulfilled_policies = 0;
+  auto conv_result = PoliciesToBitmask(policies);
+  if (conv_result) {
+    fulfilled_policies |= *conv_result;
+  } else {
+    return conv_result.GetError();
+  }
+
+  if (fulfilled_policies == 0) {
+    fulfilled_policies |= PolicyFlags::POLICY_PUBLIC;
+  }
+
+  const std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+  if (!target_apk) {
+    return Error("failed to load apk %s", target_apk_path.c_str());
+  }
+
+  std::vector<std::string> idmap_paths;
+  for (const std::string& overlay_apk_path : overlay_apk_paths) {
+    const std::string idmap_path = Idmap::CanonicalIdmapPathFor(idmap_dir, overlay_apk_path);
+    const uid_t uid = getuid();
+    if (!UidHasWriteAccessToPath(uid, idmap_path)) {
+      LOG(WARNING) << "uid " << uid << "does not have write access to " << idmap_path.c_str();
+      continue;
+    }
+
+    const std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+    if (!overlay_apk) {
+      LOG(WARNING) << "failed to load apk " << overlay_apk_path.c_str();
+      continue;
+    }
+
+    const auto idmap =
+        Idmap::FromApkAssets(*target_apk, *overlay_apk, fulfilled_policies, !ignore_overlayable);
+    if (!idmap) {
+      LOG(WARNING) << "failed to create idmap";
+      continue;
+    }
+
+    umask(kIdmapFilePermissionMask);
+    std::ofstream fout(idmap_path);
+    if (fout.fail()) {
+      LOG(WARNING) << "failed to open idmap path " << idmap_path.c_str();
+      continue;
+    }
+
+    BinaryStreamVisitor visitor(fout);
+    (*idmap)->accept(&visitor);
+    fout.close();
+    if (fout.fail()) {
+      LOG(WARNING) << "failed to write to idmap path %s" << idmap_path.c_str();
+      continue;
+    }
+
+    idmap_paths.emplace_back(idmap_path);
+  }
+
+  for (const std::string& idmap_path : idmap_paths) {
+    std::cout << idmap_path << std::endl;
+  }
+
+  return Unit{};
+}
diff --git a/cmds/idmap2/idmap2/Main.cpp b/cmds/idmap2/idmap2/Main.cpp
index 8794908..a07e793 100644
--- a/cmds/idmap2/idmap2/Main.cpp
+++ b/cmds/idmap2/idmap2/Main.cpp
@@ -53,7 +53,9 @@
 int main(int argc, char** argv) {
   SYSTRACE << "main";
   const NameToFunctionMap commands = {
-      {"create", Create}, {"dump", Dump}, {"lookup", Lookup}, {"scan", Scan}, {"verify", Verify},
+      {"create", Create}, {"create-multiple", CreateMultiple},
+      {"dump", Dump},     {"lookup", Lookup},
+      {"scan", Scan},     {"verify", Verify},
   };
   if (argc <= 1) {
     PrintUsage(commands, std::cerr);
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 6394ab2..93522d4 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -169,6 +169,11 @@
         "libcutils",
         "libstatslog",
     ],
+    apex_available: [
+        //TODO(b/149782403): Remove this once statsd no longer links against libstatsmetadata
+        "com.android.os.statsd",
+        "test_com.android.os.statsd",
+    ],
 }
 
 
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 2838ad8..0293346 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -694,7 +694,7 @@
 
     /** @hide Should this process state be considered a background state? */
     public static final boolean isProcStateBackground(int procState) {
-        return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
+        return procState > PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
     }
 
     /** @hide Is this a foreground service type? */
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index f6bbc68..9ed4798 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -97,15 +97,77 @@
 import java.util.function.Supplier;
 
 /**
- * AppOps are mappings of [package/uid, op-name] -> [mode]. The list of existing appops is defined
- * by the system and cannot be amended by apps. Only system apps can change appop-modes.
+ * App-ops are used for two purposes: Access control and tracking.
  *
- * <p>Beside a mode the system tracks when an op was {@link #noteOp noted}. The tracked data can
- * only be read by system components.
+ * <p>App-ops cover a wide variety of functionality from helping with runtime permissions access
+ * control and tracking to battery consumption tracking.
  *
- * <p>Installed apps can usually only listen to changes and events on their own ops. E.g.
- * {@link AppOpsCollector} allows to get a callback each time an app called {@link #noteOp} or
- * {@link #startOp} for an op belonging to the app.
+ * <h2>Access control</h2>
+ *
+ * <p>App-ops can either be controlled for each uid or for each package. Which one is used depends
+ * on the API provider maintaining this app-op. For any security or privacy related app-op the
+ * provider needs to control the app-op for per uid as all security and privacy is based on uid in
+ * Android.
+ *
+ * <p>To control access the app-op can be set to a mode to:
+ * <dl>
+ *     <dt>{@link #MODE_DEFAULT}
+ *     <dd>Default behavior, might differ from app-op or app-op
+ *     <dt>{@link #MODE_ALLOWED}
+ *     <dd>Allow the access
+ *     <dt>{@link #MODE_IGNORED}
+ *     <dd>Don't allow the access, i.e. don't perform the requested action or return no or dummy
+ *     data
+ *     <dt>{@link #MODE_ERRORED}
+ *     <dd>Throw a {@link SecurityException} on access. This can be suppressed by using a
+ *     {@code ...noThrow} method to check the mode
+ * </dl>
+ *
+ * <p>API providers need to check the mode returned by {@link #noteOp} if they are are allowing
+ * access to operations gated by the app-op. {@link #unsafeCheckOp} should be used to check the
+ * mode if no access is granted. E.g. this can be used for displaying app-op state in the UI or
+ * when checking the state before later calling {@link #noteOp} anyway.
+ *
+ * <p>If an operation refers to a time span (e.g. a audio-recording session) the API provider
+ * should use {@link #startOp} and {@link #finishOp} instead of {@link #noteOp}.
+ *
+ * <h3>Runtime permissions and app-ops</h3>
+ *
+ * <p>Each platform defined runtime permission (beside background modifiers) has an associated app
+ * op which is used for tracking but also to allow for silent failures. I.e. if the runtime
+ * permission is denied the caller gets a {@link SecurityException}, but if the permission is
+ * granted and the app-op is {@link #MODE_IGNORED} then the callers gets dummy behavior, e.g.
+ * location callbacks would not happen.
+ *
+ * <h3>App-op permissions</h3>
+ *
+ * <p>App-ops permissions are platform defined permissions that can be overridden. The security
+ * check for app-op permissions should by {@link #MODE_DEFAULT default} check the permission grant
+ * state. If the app-op state is set to {@link #MODE_ALLOWED} or {@link #MODE_IGNORED} the app-op
+ * state should be checked instead of the permission grant state.
+ *
+ * <p>This functionality allows to grant access by default to apps fulfilling the requirements for
+ * a certain permission level. Still the behavior can be overridden when needed.
+ *
+ * <h2>Tracking</h2>
+ *
+ * <p>App-ops track many important events, including all accesses to runtime permission protected
+ * APIs. This is done by tracking when an app-op was {@link #noteOp noted} or
+ * {@link #startOp started}. The tracked data can only be read by system components.
+ *
+ * <p><b>Only {@link #noteOp}/{@link #startOp} are tracked; {@link #unsafeCheckOp} is not tracked.
+ * Hence it is important to eventually call {@link #noteOp} or {@link #startOp} when providing
+ * access to protected operations or data.</b>
+ *
+ * <p>Some apps are forwarding access to other apps. E.g. an app might get the location from the
+ * system's location provider and then send the location further to a 3rd app. In this case the
+ * app passing on the data needs to call {@link #noteProxyOp} to signal the access proxying. This
+ * might also make sense inside of a single app if the access is forwarded between two features of
+ * the app.
+ *
+ * <p>An app can register an {@link AppOpsCollector} to get informed about what accesses the
+ * system is tracking for it. As each runtime permission has an associated app-op this API is
+ * particularly useful for an app that want to find unexpected private data accesses.
  */
 @SystemService(Context.APP_OPS_SERVICE)
 public class AppOpsManager {
@@ -121,28 +183,6 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
     public static final long CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE = 148180766L;
 
-    /**
-     * <p>App ops allows callers to:</p>
-     *
-     * <ul>
-     * <li> Note when operations are happening, and find out if they are allowed for the current
-     * caller.</li>
-     * <li> Disallow specific apps from doing specific operations.</li>
-     * <li> Collect all of the current information about operations that have been executed or
-     * are not being allowed.</li>
-     * <li> Monitor for changes in whether an operation is allowed.</li>
-     * </ul>
-     *
-     * <p>Each operation is identified by a single integer; these integers are a fixed set of
-     * operations, enumerated by the OP_* constants.
-     *
-     * <p></p>When checking operations, the result is a "mode" integer indicating the current
-     * setting for the operation under that caller: MODE_ALLOWED, MODE_IGNORED (don't execute
-     * the operation but fake its behavior enough so that the caller doesn't crash),
-     * MODE_ERRORED (throw a SecurityException back to the caller; the normal operation calls
-     * will do this for you).
-     */
-
     final Context mContext;
 
     @UnsupportedAppUsage
@@ -1093,7 +1133,7 @@
     /** Required to draw on top of other apps. */
     public static final String OPSTR_SYSTEM_ALERT_WINDOW
             = "android:system_alert_window";
-    /** Required to write/modify/update system settingss. */
+    /** Required to write/modify/update system settings. */
     public static final String OPSTR_WRITE_SETTINGS
             = "android:write_settings";
     /** @hide Get device accounts. */
@@ -6115,7 +6155,7 @@
      */
     public interface OnOpActiveChangedListener {
         /**
-         * Called when the active state of an app op changes.
+         * Called when the active state of an app-op changes.
          *
          * @param op The operation that changed.
          * @param packageName The package performing the operation.
@@ -6406,7 +6446,7 @@
     @SystemApi
     @TestApi
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
-    public void setUidMode(String appOp, int uid, @Mode int mode) {
+    public void setUidMode(@NonNull String appOp, int uid, @Mode int mode) {
         try {
             mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode);
         } catch (RemoteException e) {
@@ -6461,7 +6501,8 @@
     @TestApi
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
-    public void setMode(String op, int uid, String packageName, @Mode int mode) {
+    public void setMode(@NonNull String op, int uid, @Nullable String packageName,
+            @Mode int mode) {
         try {
             mService.setMode(strOpToOp(op), uid, packageName, mode);
         } catch (RemoteException e) {
@@ -6504,16 +6545,17 @@
     }
 
     /**
-     * Gets the app op name associated with a given permission.
-     * The app op name is one of the public constants defined
+     * Gets the app-op name associated with a given permission.
+     *
+     * <p>The app-op name is one of the public constants defined
      * in this class such as {@link #OPSTR_COARSE_LOCATION}.
      * This API is intended to be used for mapping runtime
-     * permissions to the corresponding app op.
+     * permissions to the corresponding app-op.
      *
      * @param permission The permission.
-     * @return The app op associated with the permission or null.
+     * @return The app-op associated with the permission or {@code null}.
      */
-    public static String permissionToOp(String permission) {
+    public static @Nullable String permissionToOp(@NonNull String permission) {
         final Integer opCode = sPermToOp.get(permission);
         if (opCode == null) {
             return null;
@@ -6631,7 +6673,7 @@
     }
 
     /**
-     * Start watching for changes to the active state of app ops. An app op may be
+     * Start watching for changes to the active state of app-ops. An app-op may be
      * long running and it has a clear start and stop delimiters. If an op is being
      * started or stopped by any package you will get a callback. To change the
      * watched ops for a registered callback you need to unregister and register it
@@ -6690,7 +6732,7 @@
     }
 
     /**
-     * Stop watching for changes to the active state of an app op. An app op may be
+     * Stop watching for changes to the active state of an app-op. An app-op may be
      * long running and it has a clear start and stop delimiters. Unregistering a
      * non-registered callback has no effect.
      *
@@ -6921,7 +6963,8 @@
      * @param op The operation to note.  One of the OPSTR_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
-     * @param featureId The feature in the app or {@code null} for default feature
+     * @param featureId The {@link Context#createFeatureContext feature} in the package or {@code
+     * null} for default feature
      * @param message A message describing the reason the op was noted
      *
      * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
@@ -6996,6 +7039,8 @@
      * @param op The operation to note.  One of the OPSTR_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
+     * @param featureId The {@link Context#createFeatureContext feature} in the package or {@code
+     * null} for default feature
      * @param message A message describing the reason the op was noted
      *
      * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
@@ -7003,8 +7048,8 @@
      * causing the app to crash).
      */
     public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName,
-            @Nullable String feature, @Nullable String message) {
-        return noteOpNoThrow(strOpToOp(op), uid, packageName, feature, message);
+            @Nullable String featureId, @Nullable String message) {
+        return noteOpNoThrow(strOpToOp(op), uid, packageName, featureId, message);
     }
 
     /**
@@ -7273,11 +7318,9 @@
     }
 
     /**
-     * Do a quick check to validate if a package name belongs to a UID.
-     *
-     * @throws SecurityException if the package name doesn't belong to the given
-     *             UID, or if ownership cannot be verified.
+     * @deprecated Use {@link PackageManager#getPackageUid} instead
      */
+    @Deprecated
     public void checkPackage(int uid, @NonNull String packageName) {
         try {
             if (mService.checkPackage(uid, packageName) != MODE_ALLOWED) {
@@ -7396,7 +7439,8 @@
      * @param op The operation to start.  One of the OPSTR_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
-     * @param featureId The feature in the app or {@code null} for default feature
+     * @param featureId The {@link Context#createFeatureContext feature} in the package or {@code
+     * null} for default feature
      * @param message Description why op was started
      *
      * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
@@ -7587,7 +7631,8 @@
     }
 
     /**
-     * Checks whether the given op for a package is active.
+     * Checks whether the given op for a package is active, i.e. did someone call {@link #startOp}
+     * without {@link #finishOp} yet.
      * <p>
      * If you don't hold the {@code android.Manifest.permission#WATCH_APPOPS}
      * permission you can query only for your UID.
@@ -7917,18 +7962,19 @@
     }
 
     /**
-     * Callback an app can choose to {@link #setNotedAppOpsCollector register} to monitor it's noted
-     * appops. I.e. each time any app calls {@link #noteOp} or {@link #startOp} one of the callback
-     * methods of this object is called.
+     * Callback an app can {@link #setNotedAppOpsCollector register} to monitor the app-ops the
+     * system has tracked for it. I.e. each time any app calls {@link #noteOp} or {@link #startOp}
+     * one of the callback methods of this object is called.
      *
-     * <p><b>Only appops related to dangerous permissions are collected.</b>
+     * <p><b>There will be a callback for all app-ops related to runtime permissions, but not
+     * necessarily for all other app-ops.
      *
      * <pre>
      * setNotedAppOpsCollector(new AppOpsCollector() {
      *     ArraySet<Pair<String, String>> opsNotedForThisProcess = new ArraySet<>();
      *
      *     private synchronized void addAccess(String op, String accessLocation) {
-     *         // Ops are often noted when permission protected APIs were called.
+     *         // Ops are often noted when runtime permission protected APIs were called.
      *         // In this case permissionToOp() allows to resolve the permission<->op
      *         opsNotedForThisProcess.add(new Pair(accessType, accessLocation));
      *     }
@@ -7970,20 +8016,21 @@
         }
 
         /**
-         * Called when an app-op was noted for this package inside of a two-way binder-call.
+         * Called when an app-op was {@link #noteOp noted} for this package inside of a synchronous
+         * API call, i.e. a API call that returned data or waited until the action was performed.
          *
-         * <p>Called on the calling thread just after executing the binder-call. This allows
-         * the app to e.g. collect stack traces to figure out where the access came from.
+         * <p>Called on the calling thread before the API returns. This allows the app to e.g.
+         * collect stack traces to figure out where the access came from.
          *
          * @param op The op noted
          */
         public abstract void onNoted(@NonNull SyncNotedAppOp op);
 
         /**
-         * Called when this app noted an app-op for its own package.
+         * Called when this app noted an app-op for its own package,
          *
-         * <p>Called on the thread the noted the op. This allows the app to e.g. collect stack
-         * traces to figure out where the access came from.
+         * <p>This is very similar to {@link #onNoted} only that the tracking was not caused by the
+         * API provider in a separate process, but by one in the app's own process.
          *
          * @param op The op noted
          */
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 576b56f..32e7d84 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6046,21 +6046,18 @@
 
         /**
          * Removes RemoteViews that were created for compatibility from {@param n}, if they did not
-         * change. Also removes extenders on low ram devices, as
-         * {@link android.service.notification.NotificationListenerService} services are disabled.
+         * change.
          *
          * @return {@param n}, if no stripping is needed, otherwise a stripped clone of {@param n}.
          *
          * @hide
          */
-        public static Notification maybeCloneStrippedForDelivery(Notification n, boolean isLowRam,
-                Context context) {
+        public static Notification maybeCloneStrippedForDelivery(Notification n) {
             String templateClass = n.extras.getString(EXTRA_TEMPLATE);
 
             // Only strip views for known Styles because we won't know how to
             // re-create them otherwise.
-            if (!isLowRam
-                    && !TextUtils.isEmpty(templateClass)
+            if (!TextUtils.isEmpty(templateClass)
                     && getNotificationStyleClass(templateClass) == null) {
                 return n;
             }
@@ -6077,8 +6074,7 @@
                             n.headsUpContentView.getSequenceNumber();
 
             // Nothing to do here, no need to clone.
-            if (!isLowRam
-                    && !stripContentView && !stripBigContentView && !stripHeadsUpContentView) {
+            if (!stripContentView && !stripBigContentView && !stripHeadsUpContentView) {
                 return n;
             }
 
@@ -6095,15 +6091,6 @@
                 clone.headsUpContentView = null;
                 clone.extras.remove(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT);
             }
-            if (isLowRam) {
-                String[] allowedServices = context.getResources().getStringArray(
-                        R.array.config_allowedManagedServicesOnLowRamDevices);
-                if (allowedServices.length == 0) {
-                    clone.extras.remove(Notification.TvExtender.EXTRA_TV_EXTENDER);
-                    clone.extras.remove(WearableExtender.EXTRA_WEARABLE_EXTENSIONS);
-                    clone.extras.remove(CarExtender.EXTRA_CAR_EXTENDER);
-                }
-            }
             return clone;
         }
 
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 528b508..88edb05 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -592,10 +592,7 @@
         }
 
         notification.reduceImageSizes(mContext);
-
-        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-        boolean isLowRam = am.isLowRamDevice();
-        return Builder.maybeCloneStrippedForDelivery(notification, isLowRam, mContext);
+        return Builder.maybeCloneStrippedForDelivery(notification);
     }
 
     private void fixLegacySmallIcon(Notification n, String pkg) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2e591ca..acc4cb0 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6478,6 +6478,21 @@
     public static final int FLAG_ACTIVITY_MATCH_EXTERNAL = 0x00000800;
 
     /**
+     * If set in an intent passed to {@link Context#startActivity Context.startActivity()}, this
+     * flag will only launch the intent if it resolves to a result that is not a browser. If no such
+     * result exists, an {@link ActivityNotFoundException} will be thrown.
+     */
+    public static final int FLAG_ACTIVITY_REQUIRE_NON_BROWSER = 0x00000400;
+
+    /**
+     * If set in an intent passed to {@link Context#startActivity Context.startActivity()}, this
+     * flag will only launch the intent if it resolves to a single result. If no such result exists
+     * or if the system chooser would otherwise be displayed, an {@link ActivityNotFoundException}
+     * will be thrown.
+     */
+    public static final int FLAG_ACTIVITY_REQUIRE_DEFAULT = 0x00000200;
+
+    /**
      * If set, when sending a broadcast only registered receivers will be
      * called -- no BroadcastReceiver components will be launched.
      */
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index a3487be..55a6cab 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -45,7 +45,7 @@
             STATE_NO_IDMAP,
             STATE_DISABLED,
             STATE_ENABLED,
-            STATE_ENABLED_STATIC,
+            STATE_ENABLED_IMMUTABLE,
             // @Deprecated STATE_TARGET_IS_BEING_REPLACED,
             STATE_OVERLAY_IS_BEING_REPLACED,
     })
@@ -117,11 +117,12 @@
 
     /**
      * The overlay package is currently enabled because it is marked as
-     * 'static'. It cannot be disabled but will change state if for instance
+     * 'immutable'. It cannot be disabled but will change state if for instance
      * its target is uninstalled.
      * @hide
      */
-    public static final int STATE_ENABLED_STATIC = 6;
+    @Deprecated
+    public static final int STATE_ENABLED_IMMUTABLE = 6;
 
     /**
      * Overlay category: theme.
@@ -180,21 +181,21 @@
     public final int userId;
 
     /**
-     * Priority as read from the manifest. Used if isStatic is true. Not
-     * intended to be exposed to 3rd party.
+     * Priority as configured by {@link com.android.internal.content.om.OverlayConfig}.
+     * Not intended to be exposed to 3rd party.
      *
      * @hide
      */
     public final int priority;
 
     /**
-     * isStatic as read from the manifest. If true, the overlay is
-     * unconditionally loaded and cannot be unloaded. Not intended to be
+     * isMutable as configured by {@link com.android.internal.content.om.OverlayConfig}.
+     * If false, the overlay is unconditionally loaded and cannot be unloaded. Not intended to be
      * exposed to 3rd party.
      *
      * @hide
      */
-    public final boolean isStatic;
+    public final boolean isMutable;
 
     /**
      * Create a new OverlayInfo based on source with an updated state.
@@ -207,14 +208,14 @@
     public OverlayInfo(@NonNull OverlayInfo source, @State int state) {
         this(source.packageName, source.targetPackageName, source.targetOverlayableName,
                 source.category, source.baseCodePath, state, source.userId, source.priority,
-                source.isStatic);
+                source.isMutable);
     }
 
     /** @hide */
     public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName,
             @Nullable String targetOverlayableName, @Nullable String category,
             @NonNull String baseCodePath, int state, int userId,
-            int priority, boolean isStatic) {
+            int priority, boolean isMutable) {
         this.packageName = packageName;
         this.targetPackageName = targetPackageName;
         this.targetOverlayableName = targetOverlayableName;
@@ -223,7 +224,7 @@
         this.state = state;
         this.userId = userId;
         this.priority = priority;
-        this.isStatic = isStatic;
+        this.isMutable = isMutable;
         ensureValidState();
     }
 
@@ -237,7 +238,7 @@
         state = source.readInt();
         userId = source.readInt();
         priority = source.readInt();
-        isStatic = source.readBoolean();
+        isMutable = source.readBoolean();
         ensureValidState();
     }
 
@@ -307,7 +308,7 @@
             case STATE_NO_IDMAP:
             case STATE_DISABLED:
             case STATE_ENABLED:
-            case STATE_ENABLED_STATIC:
+            case STATE_ENABLED_IMMUTABLE:
             case STATE_TARGET_IS_BEING_REPLACED:
             case STATE_OVERLAY_IS_BEING_REPLACED:
                 break;
@@ -331,7 +332,7 @@
         dest.writeInt(state);
         dest.writeInt(userId);
         dest.writeInt(priority);
-        dest.writeBoolean(isStatic);
+        dest.writeBoolean(isMutable);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<OverlayInfo> CREATOR =
@@ -360,7 +361,7 @@
     public boolean isEnabled() {
         switch (state) {
             case STATE_ENABLED:
-            case STATE_ENABLED_STATIC:
+            case STATE_ENABLED_IMMUTABLE:
                 return true;
             default:
                 return false;
@@ -386,8 +387,8 @@
                 return "STATE_DISABLED";
             case STATE_ENABLED:
                 return "STATE_ENABLED";
-            case STATE_ENABLED_STATIC:
-                return "STATE_ENABLED_STATIC";
+            case STATE_ENABLED_IMMUTABLE:
+                return "STATE_ENABLED_IMMUTABLE";
             case STATE_TARGET_IS_BEING_REPLACED:
                 return "STATE_TARGET_IS_BEING_REPLACED";
             case STATE_OVERLAY_IS_BEING_REPLACED:
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 03ed373..5a56b0e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3023,6 +3023,18 @@
     public static final String FEATURE_TUNER = "android.hardware.tv.tuner";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+     * the necessary changes to support app enumeration.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_APP_ENUMERATION = "android.software.app_enumeration";
+
+    /** @hide */
+    public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
+
+    /**
      * Extra field name for the URI to a verification file. Passed to a package
      * verifier.
      *
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index da44f70..168679e 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -483,6 +483,9 @@
         public final boolean isolatedSplits;
         public final boolean isSplitRequired;
         public final boolean useEmbeddedDex;
+        public final String targetPackageName;
+        public final boolean overlayIsStatic;
+        public final int overlayPriority;
 
         public ApkLite(String codePath, String packageName, String splitName,
                 boolean isFeatureSplit,
@@ -492,6 +495,7 @@
                 SigningDetails signingDetails, boolean coreApp,
                 boolean debuggable, boolean multiArch, boolean use32bitAbi,
                 boolean useEmbeddedDex, boolean extractNativeLibs, boolean isolatedSplits,
+                String targetPackageName, boolean overlayIsStatic, int overlayPriority,
                 int minSdkVersion, int targetSdkVersion) {
             this.codePath = codePath;
             this.packageName = packageName;
@@ -513,6 +517,9 @@
             this.extractNativeLibs = extractNativeLibs;
             this.isolatedSplits = isolatedSplits;
             this.isSplitRequired = isSplitRequired;
+            this.targetPackageName = targetPackageName;
+            this.overlayIsStatic = overlayIsStatic;
+            this.overlayPriority = overlayPriority;
             this.minSdkVersion = minSdkVersion;
             this.targetSdkVersion = targetSdkVersion;
         }
@@ -1784,6 +1791,12 @@
         boolean useEmbeddedDex = false;
         String configForSplit = null;
         String usesSplitName = null;
+        String targetPackage = null;
+        boolean overlayIsStatic = false;
+        int overlayPriority = 0;
+
+        String requiredSystemPropertyName = null;
+        String requiredSystemPropertyValue = null;
 
         for (int i = 0; i < attrs.getAttributeCount(); i++) {
             final String attr = attrs.getAttributeName(i);
@@ -1848,6 +1861,21 @@
                         useEmbeddedDex = attrs.getAttributeBooleanValue(i, false);
                     }
                 }
+            } else if (PackageParser.TAG_OVERLAY.equals(parser.getName())) {
+                for (int i = 0; i < attrs.getAttributeCount(); ++i) {
+                    final String attr = attrs.getAttributeName(i);
+                    if ("requiredSystemPropertyName".equals(attr)) {
+                        requiredSystemPropertyName = attrs.getAttributeValue(i);
+                    } else if ("requiredSystemPropertyValue".equals(attr)) {
+                        requiredSystemPropertyValue = attrs.getAttributeValue(i);
+                    } else if ("targetPackage".equals(attr)) {
+                        targetPackage = attrs.getAttributeValue(i);;
+                    } else if ("isStatic".equals(attr)) {
+                        overlayIsStatic = attrs.getAttributeBooleanValue(i, false);
+                    } else if ("priority".equals(attr)) {
+                        overlayPriority = attrs.getAttributeIntValue(i, 0);
+                    }
+                }
             } else if (TAG_USES_SPLIT.equals(parser.getName())) {
                 if (usesSplitName != null) {
                     Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
@@ -1874,11 +1902,22 @@
             }
         }
 
+        // Check to see if overlay should be excluded based on system property condition
+        if (!checkRequiredSystemProperty(requiredSystemPropertyName,
+                requiredSystemPropertyValue)) {
+            Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and "
+                    + codePath + ": overlay ignored due to required system property: "
+                    + requiredSystemPropertyName + " with value: " + requiredSystemPropertyValue);
+            targetPackage = null;
+            overlayIsStatic = false;
+            overlayPriority = 0;
+        }
+
         return new ApkLite(codePath, packageSplit.first, packageSplit.second, isFeatureSplit,
                 configForSplit, usesSplitName, isSplitRequired, versionCode, versionCodeMajor,
                 revisionCode, installLocation, verifiers, signingDetails, coreApp, debuggable,
                 multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs, isolatedSplits,
-                minSdkVersion, targetSdkVersion);
+                targetPackage, overlayIsStatic, overlayPriority, minSdkVersion, targetSdkVersion);
     }
 
     /**
@@ -2162,7 +2201,7 @@
                 }
 
                 // check to see if overlay should be excluded based on system property condition
-                if (!checkOverlayRequiredSystemProperty(propName, propValue)) {
+                if (!checkRequiredSystemProperty(propName, propValue)) {
                     Slog.i(TAG, "Skipping target and overlay pair " + pkg.mOverlayTarget + " and "
                         + pkg.baseCodePath+ ": overlay ignored due to required system property: "
                         + propName + " with value: " + propValue);
@@ -2590,8 +2629,11 @@
         return pkg;
     }
 
-    private boolean checkOverlayRequiredSystemProperty(String propName, String propValue) {
-
+    /**
+     * Returns {@code true} if both the property name and value are empty or if the given system
+     * property is set to the specified value. In all other cases, returns {@code false}
+     */
+    public static boolean checkRequiredSystemProperty(String propName, String propValue) {
         if (TextUtils.isEmpty(propName) || TextUtils.isEmpty(propValue)) {
             if (!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue)) {
                 // malformed condition - incomplete
diff --git a/core/java/android/content/pm/PackagePartitions.java b/core/java/android/content/pm/PackagePartitions.java
new file mode 100644
index 0000000..9b8396e
--- /dev/null
+++ b/core/java/android/content/pm/PackagePartitions.java
@@ -0,0 +1,213 @@
+/*
+ * 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.content.pm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Environment;
+import android.os.FileUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.function.Function;
+
+/**
+ * Exposes {@link #SYSTEM_PARTITIONS} which represents the partitions in which application packages
+ * can be installed. The partitions are ordered from most generic (lowest priority) to most specific
+ * (greatest priority).
+ *
+ * @hide
+ **/
+public class PackagePartitions {
+    public static final int PARTITION_SYSTEM = 0;
+    public static final int PARTITION_VENDOR = 1;
+    public static final int PARTITION_ODM = 2;
+    public static final int PARTITION_OEM = 3;
+    public static final int PARTITION_PRODUCT = 4;
+    public static final int PARTITION_SYSTEM_EXT = 5;
+
+    @IntDef(flag = true, prefix = { "PARTITION_" }, value = {
+        PARTITION_SYSTEM,
+        PARTITION_VENDOR,
+        PARTITION_ODM,
+        PARTITION_OEM,
+        PARTITION_PRODUCT,
+        PARTITION_SYSTEM_EXT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PartitionType {}
+
+    /**
+     * The list of all system partitions that may contain packages in ascending order of
+     * specificity (the more generic, the earlier in the list a partition appears).
+     */
+    private static final ArrayList<SystemPartition> SYSTEM_PARTITIONS =
+            new ArrayList<>(Arrays.asList(
+                    new SystemPartition(Environment.getRootDirectory(), PARTITION_SYSTEM,
+                            true /* containsPrivApp */, false /* containsOverlay */),
+                    new SystemPartition(Environment.getVendorDirectory(), PARTITION_VENDOR,
+                            true /* containsPrivApp */, true /* containsOverlay */),
+                    new SystemPartition(Environment.getOdmDirectory(), PARTITION_ODM,
+                            true /* containsPrivApp */, true /* containsOverlay */),
+                    new SystemPartition(Environment.getOemDirectory(), PARTITION_OEM,
+                            false /* containsPrivApp */, true /* containsOverlay */),
+                    new SystemPartition(Environment.getProductDirectory(), PARTITION_PRODUCT,
+                            true /* containsPrivApp */, true /* containsOverlay */),
+                    new SystemPartition(Environment.getSystemExtDirectory(), PARTITION_SYSTEM_EXT,
+                            true /* containsPrivApp */, true /* containsOverlay */)));
+
+    /**
+     * Returns a list in which the elements are products of the specified function applied to the
+     * list of {@link #SYSTEM_PARTITIONS} in increasing specificity order.
+     */
+    public static <T> ArrayList<T> getOrderedPartitions(
+            @NonNull Function<SystemPartition, T> producer) {
+        final ArrayList<T> out = new ArrayList<>();
+        for (int i = 0, n = SYSTEM_PARTITIONS.size(); i < n; i++) {
+            final T v = producer.apply(SYSTEM_PARTITIONS.get(i));
+            if (v != null)  {
+                out.add(v);
+            }
+        }
+        return out;
+    }
+
+    /** Represents a partition that contains application packages. */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public static class SystemPartition {
+        @NonNull
+        public final File folder;
+
+        @PartitionType
+        public final int type;
+
+        @Nullable
+        private final DeferredCanonicalFile mAppFolder;
+
+        @Nullable
+        private final DeferredCanonicalFile mPrivAppFolder;
+
+        @Nullable
+        private final DeferredCanonicalFile mOverlayFolder;
+
+        private SystemPartition(@NonNull File folder, @PartitionType int type,
+                boolean containsPrivApp, boolean containsOverlay) {
+            this.folder = folder;
+            this.type = type;
+            this.mAppFolder = new DeferredCanonicalFile(folder, "app");
+            this.mPrivAppFolder = containsPrivApp ?
+                    new DeferredCanonicalFile(folder, "priv-app") : null;
+            this.mOverlayFolder = containsOverlay ?
+                    new DeferredCanonicalFile(folder, "overlay") : null;
+        }
+
+        public SystemPartition(@NonNull SystemPartition original) {
+            this.folder = original.folder;
+            this.type = original.type;
+            this.mAppFolder = original.mAppFolder;
+            this.mPrivAppFolder = original.mPrivAppFolder;
+            this.mOverlayFolder = original.mOverlayFolder;
+        }
+
+        /**
+         * Creates a partition containing the same folders as the original partition but with a
+         * different root folder.
+         */
+        public SystemPartition(@NonNull File rootFolder, @NonNull SystemPartition partition) {
+            this(rootFolder, partition.type, partition.mPrivAppFolder != null,
+                    partition.mOverlayFolder != null);
+        }
+
+        /** Returns the canonical app folder of the partition. */
+        @Nullable
+        public File getAppFolder() {
+            return mAppFolder == null ? null : mAppFolder.getFile();
+        }
+
+        /** Returns the canonical priv-app folder of the partition, if one exists. */
+        @Nullable
+        public File getPrivAppFolder() {
+            return mPrivAppFolder == null ? null : mPrivAppFolder.getFile();
+        }
+
+        /** Returns the canonical overlay folder of the partition, if one exists. */
+        @Nullable
+        public File getOverlayFolder() {
+            return mOverlayFolder == null ? null : mOverlayFolder.getFile();
+        }
+
+        /** Returns whether the partition contains the specified file in its priv-app folder. */
+        public boolean containsPrivApp(@NonNull File scanFile) {
+            return FileUtils.contains(mPrivAppFolder.getFile(), scanFile);
+        }
+
+        /** Returns whether the partition contains the specified file in its app folder. */
+        public boolean containsApp(@NonNull File scanFile) {
+            return FileUtils.contains(mAppFolder.getFile(), scanFile);
+        }
+
+        /** Returns whether the partition contains the specified file in its overlay folder. */
+        public boolean containsOverlay(@NonNull File scanFile) {
+            return FileUtils.contains(mOverlayFolder.getFile(), scanFile);
+        }
+
+        /** Returns whether the partition contains the specified file. */
+        public boolean containsPath(@NonNull String path) {
+            return path.startsWith(folder.getPath() + "/");
+        }
+
+        /** Returns whether the partition contains the specified file in its priv-app folder. */
+        public boolean containsPrivPath(@NonNull String path) {
+            return mPrivAppFolder != null
+                    && path.startsWith(mPrivAppFolder.getFile().getPath() + "/");
+        }
+    }
+
+    /**
+     * A class that defers the canonicalization of its underlying file. This must be done so
+     * processes do not attempt to canonicalize files in directories for which the process does not
+     * have the correct selinux policies.
+     */
+    private static class DeferredCanonicalFile {
+        private boolean mIsCanonical;
+        private File mFile;
+        private DeferredCanonicalFile(File dir, String fileName) {
+            mFile = new File(dir, fileName);
+            mIsCanonical = false;
+        }
+
+        private File getFile() {
+            if (mIsCanonical) {
+                return mFile;
+            }
+            mIsCanonical = true;
+            try {
+                mFile = mFile.getCanonicalFile();
+            } catch (IOException ignore) {
+                // failed to look up canonical path, continue with original one
+            }
+            return mFile;
+        }
+    }
+}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 5be9c91..9087f42 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -289,6 +289,12 @@
         boolean useEmbeddedDex = false;
         String configForSplit = null;
         String usesSplitName = null;
+        String targetPackage = null;
+        boolean overlayIsStatic = false;
+        int overlayPriority = 0;
+
+        String requiredSystemPropertyName = null;
+        String requiredSystemPropertyValue = null;
 
         for (int i = 0; i < attrs.getAttributeCount(); i++) {
             final String attr = attrs.getAttributeName(i);
@@ -365,6 +371,21 @@
                             break;
                     }
                 }
+            } else if (PackageParser.TAG_OVERLAY.equals(parser.getName())) {
+                for (int i = 0; i < attrs.getAttributeCount(); ++i) {
+                    final String attr = attrs.getAttributeName(i);
+                    if ("requiredSystemPropertyName".equals(attr)) {
+                        requiredSystemPropertyName = attrs.getAttributeValue(i);
+                    } else if ("requiredSystemPropertyValue".equals(attr)) {
+                        requiredSystemPropertyValue = attrs.getAttributeValue(i);
+                    } else if ("targetPackage".equals(attr)) {
+                        targetPackage = attrs.getAttributeValue(i);;
+                    } else if ("isStatic".equals(attr)) {
+                        overlayIsStatic = attrs.getAttributeBooleanValue(i, false);
+                    } else if ("priority".equals(attr)) {
+                        overlayPriority = attrs.getAttributeIntValue(i, 0);
+                    }
+                }
             } else if (PackageParser.TAG_USES_SPLIT.equals(parser.getName())) {
                 if (usesSplitName != null) {
                     Slog.w(TAG, "Only one <uses-split> permitted. Ignoring others.");
@@ -391,11 +412,23 @@
             }
         }
 
+        // Check to see if overlay should be excluded based on system property condition
+        if (!PackageParser.checkRequiredSystemProperty(requiredSystemPropertyName,
+                requiredSystemPropertyValue)) {
+            Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and "
+                    + codePath + ": overlay ignored due to required system property: "
+                    + requiredSystemPropertyName + " with value: " + requiredSystemPropertyValue);
+            targetPackage = null;
+            overlayIsStatic = false;
+            overlayPriority = 0;
+        }
+
         return new PackageParser.ApkLite(codePath, packageSplit.first, packageSplit.second,
                 isFeatureSplit, configForSplit, usesSplitName, isSplitRequired, versionCode,
                 versionCodeMajor, revisionCode, installLocation, verifiers, signingDetails,
                 coreApp, debuggable, multiArch, use32bitAbi, useEmbeddedDex, extractNativeLibs,
-                isolatedSplits, minSdkVersion, targetSdkVersion);
+                isolatedSplits, targetPackage, overlayIsStatic, overlayPriority, minSdkVersion,
+                targetSdkVersion);
     }
 
     public static VerifierInfo parseVerifier(AttributeSet attrs) {
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 1b01758..f295f8c 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -38,6 +38,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.om.OverlayConfig;
 
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
@@ -242,14 +243,11 @@
         try {
             final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
             apkAssets.add(ApkAssets.loadFromPath(frameworkPath, true /*system*/));
-            final String[] systemIdmapPaths = nativeCreateIdmapsForStaticOverlaysTargetingAndroid();
-            if (systemIdmapPaths != null) {
-                for (String idmapPath : systemIdmapPaths) {
-                    apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
-                }
-            } else {
-                Log.w(TAG, "'idmap2 --scan' failed: no static=\"true\" overlays targeting "
-                        + "\"android\" will be loaded");
+
+            final String[] systemIdmapPaths =
+                    OverlayConfig.getZygoteInstance().createImmutableFrameworkIdmapsInZygote();
+            for (String idmapPath : systemIdmapPaths) {
+                apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
             }
 
             sSystemApkAssetsSet = new ArraySet<>(apkAssets);
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index f19ba0f..2041cfb 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -126,7 +126,11 @@
         return getIntentForConfirmation();
     }
 
-    /** Delete the VPN profile configuration that was provisioned by the calling app */
+    /**
+     * Delete the VPN profile configuration that was provisioned by the calling app
+     *
+     * @throws SecurityException if this would violate user settings
+     */
     public void deleteProvisionedVpnProfile() {
         try {
             mService.deleteVpnProfile(mContext.getOpPackageName());
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index 7d3b13b..f37e01d 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -58,7 +58,7 @@
  * &lt;/service></pre>
  *
  *  <p> Condition providers cannot be bound by the system on
- * {@link ActivityManager#isLowRamDevice() low ram} devices</p>
+ * {@link ActivityManager#isLowRamDevice() low ram} devices running Android Q (and below)</p>
  *
  * @deprecated Instead of using an automatically bound service, use
  * {@link android.app.NotificationManager#setAutomaticZenRuleState(String, Condition)} to tell the
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 0ff2e03..6562572 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -86,8 +86,8 @@
  * or after {@link #onListenerDisconnected()}.
  * </p>
  * <p> Notification listeners cannot get notification access or be bound by the system on
- * {@linkplain ActivityManager#isLowRamDevice() low-RAM} devices. The system also ignores
- * notification listeners running in a work profile. A
+ * {@linkplain ActivityManager#isLowRamDevice() low-RAM} devices running Android Q (and below).
+ * The system also ignores notification listeners running in a work profile. A
  * {@link android.app.admin.DevicePolicyManager} might block notifications originating from a work
  * profile.</p>
  * <p>
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 44ab596..043e5be 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -666,7 +666,22 @@
         int localChanges;
     }
 
+    // If set, ViewRootImpl will call BLASTBufferQueue::setNextTransaction with
+    // mRtBLASTSyncTransaction, prior to invoking draw. This provides a way
+    // to redirect the buffers in to transactions.
     private boolean mNextDrawUseBLASTSyncTransaction;
+    // Set when calling setNextTransaction, we can't just reuse mNextDrawUseBLASTSyncTransaction
+    // because, imagine this scenario:
+    //     1. First draw is using BLAST, mNextDrawUseBLAST = true
+    //     2. We call perform draw and are waiting on the callback
+    //     3. After the first perform draw but before the first callback and the
+    //        second perform draw, a second draw sets mNextDrawUseBLAST = true (it already was)
+    //     4. At this point the callback fires and we set mNextDrawUseBLAST = false;
+    //     5. We get to performDraw and fail to sync as we intended because mNextDrawUseBLAST
+    //        is now false.
+    // This is why we use a two-step latch with the two booleans, one consumed from
+    // performDraw and one consumed from finishBLASTSync()
+    private boolean mNextReportConsumeBLAST;
     // Be very careful with the threading here. This is used from the render thread while
     // the UI thread is paused and then applied and cleared from the UI thread right after
     // draw returns.
@@ -2206,8 +2221,9 @@
         return insets;
     }
 
-    void dispatchApplyInsets(View host) {
+    public void dispatchApplyInsets(View host) {
         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "dispatchApplyInsets");
+        mApplyInsetsRequested = false;
         WindowInsets insets = getWindowInsets(true /* forceConstruct */);
         final boolean dispatchCutout = (mWindowAttributes.layoutInDisplayCutoutMode
                 == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS);
@@ -2444,7 +2460,6 @@
         }
 
         if (mApplyInsetsRequested) {
-            mApplyInsetsRequested = false;
             updateVisibleInsets();
             dispatchApplyInsets(host);
             if (mLayoutRequested) {
@@ -2621,7 +2636,6 @@
                 if (contentInsetsChanged || mLastSystemUiVisibility !=
                         mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested) {
                     mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
-                    mApplyInsetsRequested = false;
                     dispatchApplyInsets(host);
                     // We applied insets so force contentInsetsChanged to ensure the
                     // hierarchy is measured below.
@@ -3719,9 +3733,9 @@
             usingAsyncReport = mReportNextDraw;
             if (needFrameCompleteCallback) {
                 final Handler handler = mAttachInfo.mHandler;
-                mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) ->
+                mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> {
+                        finishBLASTSync();
                         handler.postAtFrontOfQueue(() -> {
-                            finishBLASTSync();
                             if (reportNextDraw) {
                                 // TODO: Use the frame number
                                 pendingDrawFinished();
@@ -3731,12 +3745,23 @@
                                     commitCallbacks.get(i).run();
                                 }
                             }
-                        }));
+                        });});
             }
         }
 
         try {
             if (mNextDrawUseBLASTSyncTransaction) {
+                // TODO(b/149747443)
+                // We aren't prepared to handle overlapping use of mRtBLASTSyncTransaction
+                // so if we are BLAST syncing we make sure the previous draw has
+                // totally finished.
+                if (mAttachInfo.mThreadedRenderer != null) {
+                    mAttachInfo.mThreadedRenderer.fence();
+                }
+
+                mNextReportConsumeBLAST = true;
+                mNextDrawUseBLASTSyncTransaction = false;
+
                 mBlastBufferQueue.setNextTransaction(mRtBLASTSyncTransaction);
             }
             boolean canUseAsync = draw(fullRedrawNeeded);
@@ -9556,8 +9581,8 @@
     }
 
     private void finishBLASTSync() {
-        if (mNextDrawUseBLASTSyncTransaction) {
-            mNextDrawUseBLASTSyncTransaction = false;
+        if (mNextReportConsumeBLAST) {
+            mNextReportConsumeBLAST = false;
             mRtBLASTSyncTransaction.apply();
         }
     }
@@ -9566,7 +9591,10 @@
         return mRtBLASTSyncTransaction;
     }
 
-    SurfaceControl getRenderSurfaceControl() {
+    /**
+     * @hide
+     */
+    public SurfaceControl getRenderSurfaceControl() {
         if (mUseBLASTAdapter) {
             return mBlastSurfaceControl;
         } else {
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 54bdb88..ce7cfa7 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1794,6 +1794,9 @@
             client.autofillClientResetableStateAvailable();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
+        } catch (SyncResultReceiver.TimeoutException e) {
+            // no-op, just log the error message.
+            Log.w(TAG, "Exception getting result from SyncResultReceiver: " + e);
         }
     }
 
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index a6a5ec5..47ea1cb 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -1257,7 +1257,8 @@
                             return;
                         }
                         // Show or move the window at the content draw frame.
-                        mTransaction.deferTransactionUntilSurface(mSurfaceControl, mSurface, frame);
+                        mTransaction.deferTransactionUntil(mSurfaceControl, mSurfaceControl,
+                                frame);
                         if (updateWindowPosition) {
                             mTransaction.setPosition(mSurfaceControl, pendingX, pendingY);
                         }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 5dc8b0b..8f0ffd9 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1304,8 +1304,11 @@
                     + "cannot be null.");
         }
         // We partially rebuild the inactive adapter to determine if we should auto launch
-        boolean rebuildActiveCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true);
-        boolean rebuildInactiveCompleted = mMultiProfilePagerAdapter.rebuildInactiveTab(false);
+        boolean rebuildCompleted = mMultiProfilePagerAdapter.rebuildActiveTab(true);
+        if (hasWorkProfile() && ENABLE_TABBED_VIEW) {
+            boolean rebuildInactiveCompleted = mMultiProfilePagerAdapter.rebuildInactiveTab(false);
+            rebuildCompleted = rebuildCompleted && rebuildInactiveCompleted;
+        }
 
         if (useLayoutWithDefault()) {
             mLayoutId = R.layout.resolver_list_with_default;
@@ -1314,7 +1317,7 @@
         }
         setContentView(mLayoutId);
         mMultiProfilePagerAdapter.setupViewPager(findViewById(R.id.profile_pager));
-        return postRebuildList(rebuildActiveCompleted && rebuildInactiveCompleted);
+        return postRebuildList(rebuildCompleted);
     }
 
     /**
@@ -1378,6 +1381,10 @@
             return false;
         }
 
+        if (mMultiProfilePagerAdapter.getActiveListAdapter().getOtherProfile() != null) {
+            return false;
+        }
+
         // Only one target, so we're a candidate to auto-launch!
         final TargetInfo target = mMultiProfilePagerAdapter.getActiveListAdapter()
                 .targetInfoForPosition(0, false);
diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java
new file mode 100644
index 0000000..f699eb8
--- /dev/null
+++ b/core/java/com/android/internal/content/om/OverlayConfig.java
@@ -0,0 +1,413 @@
+/*
+ * 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.internal.content.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackagePartitions;
+import android.content.pm.parsing.AndroidPackage;
+import android.os.Build;
+import android.os.Process;
+import android.os.Trace;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.om.OverlayConfigParser.OverlayPartition;
+import com.android.internal.content.om.OverlayConfigParser.ParsedConfiguration;
+import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Responsible for reading overlay configuration files and handling queries of overlay mutability,
+ * default-enabled state, and priority.
+ *
+ * @see OverlayConfigParser
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class OverlayConfig {
+    static final String TAG = "OverlayConfig";
+
+    // The default priority of an overlay that has not been configured. Overlays with default
+    // priority have a higher precedence than configured overlays.
+    @VisibleForTesting
+    public static final int DEFAULT_PRIORITY = Integer.MAX_VALUE;
+
+    @VisibleForTesting
+    public static final class Configuration {
+        @Nullable
+        public final ParsedConfiguration parsedConfig;
+
+        public final int configIndex;
+
+        public Configuration(@Nullable ParsedConfiguration parsedConfig, int configIndex) {
+            this.parsedConfig = parsedConfig;
+            this.configIndex = configIndex;
+        }
+    }
+
+    /**
+     * Interface for providing information on scanned packages.
+     * TODO(147840005): Remove this when android:isStatic and android:priority are fully deprecated
+     */
+    public interface AndroidPackageProvider {
+
+        /** Performs the given action for each package. */
+        void forEachPackage(Consumer<AndroidPackage> p);
+    }
+
+    private static final Comparator<ParsedConfiguration> sStaticOverlayComparator = (c1, c2) -> {
+        final ParsedOverlayInfo o1 = c1.parsedInfo;
+        final ParsedOverlayInfo o2 = c2.parsedInfo;
+        Preconditions.checkArgument(o1.isStatic && o2.isStatic,
+                "attempted to sort non-static overlay");
+
+        if (!o1.targetPackageName.equals(o2.targetPackageName)) {
+            return o1.targetPackageName.compareTo(o2.targetPackageName);
+        }
+
+        final int comparedPriority = o1.priority - o2.priority;
+        return comparedPriority == 0 ? o1.path.compareTo(o2.path) : comparedPriority;
+    };
+
+    // Map of overlay package name to configured overlay settings
+    private final ArrayMap<String, Configuration> mConfigurations = new ArrayMap<>();
+
+    // Singleton instance only assigned in system server
+    private static OverlayConfig sInstance;
+
+    @VisibleForTesting
+    public OverlayConfig(@Nullable File rootDirectory,
+            @Nullable Supplier<OverlayScanner> scannerFactory,
+            @Nullable AndroidPackageProvider packageProvider) {
+        Preconditions.checkArgument((scannerFactory == null) != (packageProvider == null),
+                "scannerFactory and packageProvider cannot be both null or both non-null");
+
+        final ArrayList<OverlayPartition> partitions;
+        if (rootDirectory == null) {
+            partitions = new ArrayList<>(
+                    PackagePartitions.getOrderedPartitions(OverlayPartition::new));
+        } else {
+            // Rebase the system partitions and settings file on the specified root directory.
+            partitions = new ArrayList<>(PackagePartitions.getOrderedPartitions(
+                    p -> new OverlayPartition(new File(rootDirectory, p.folder.getPath()), p)));
+        }
+
+        boolean foundConfigFile = false;
+        ArrayList<ParsedOverlayInfo> packageManagerOverlayInfos = null;
+
+        final ArrayList<ParsedConfiguration> overlays = new ArrayList<>();
+        for (int i = 0, n = partitions.size(); i < n; i++) {
+            final OverlayPartition partition = partitions.get(i);
+            final OverlayScanner scanner = (scannerFactory == null) ? null : scannerFactory.get();
+            final ArrayList<ParsedConfiguration> partitionOverlays =
+                    OverlayConfigParser.getConfigurations(partition, scanner);
+            if (partitionOverlays != null) {
+                foundConfigFile = true;
+                overlays.addAll(partitionOverlays);
+                continue;
+            }
+
+            // If the configuration file is not present, then use android:isStatic and
+            // android:priority to configure the overlays in the partition.
+            // TODO(147840005): Remove converting static overlays to immutable, default-enabled
+            //  overlays when android:siStatic and android:priority are fully deprecated.
+            final ArrayList<ParsedOverlayInfo> partitionOverlayInfos;
+            if (scannerFactory != null) {
+                partitionOverlayInfos = new ArrayList<>(scanner.getAllParsedInfos());
+            } else {
+                if (packageManagerOverlayInfos == null) {
+                    packageManagerOverlayInfos = getOverlayPackageInfos(packageProvider);
+                }
+
+                // Filter out overlays not present in the partition.
+                partitionOverlayInfos = new ArrayList<>(packageManagerOverlayInfos);
+                for (int j = partitionOverlayInfos.size() - 1; j >= 0; j--) {
+                    if (!partition.containsPath(partitionOverlayInfos.get(j).path.getPath())) {
+                        partitionOverlayInfos.remove(j);
+                    }
+                }
+            }
+
+            // Static overlays are configured as immutable, default-enabled overlays.
+            final ArrayList<ParsedConfiguration> partitionConfigs = new ArrayList<>();
+            for (int j = 0, m = partitionOverlayInfos.size(); j < m; j++) {
+                final ParsedOverlayInfo p = partitionOverlayInfos.get(j);
+                if (p.isStatic) {
+                    partitionConfigs.add(new ParsedConfiguration(p.packageName,
+                            true /* enabled */, false /* mutable */, partition.policy, p));
+                }
+            }
+
+            partitionConfigs.sort(sStaticOverlayComparator);
+            overlays.addAll(partitionConfigs);
+        }
+
+        if (!foundConfigFile) {
+            // If no overlay configuration files exist, disregard partition precedence and allow
+            // android:priority to reorder overlays across partition boundaries.
+            overlays.sort(sStaticOverlayComparator);
+        }
+
+        for (int i = 0, n = overlays.size(); i < n; i++) {
+            // Add the configurations to a map so definitions of an overlay in an earlier
+            // partition can be replaced by an overlay with the same package name in a later
+            // partition.
+            final ParsedConfiguration config = overlays.get(i);
+            mConfigurations.put(config.packageName, new Configuration(config, i));
+        }
+    }
+
+    /**
+     * Creates an instance of OverlayConfig for use in the zygote process.
+     * This instance will not include information of static overlays existing outside of a partition
+     * overlay directory.
+     */
+    @NonNull
+    public static OverlayConfig getZygoteInstance() {
+        if (Process.myUid() != Process.ROOT_UID) {
+            // Scan the overlays in the zygote process to generate configuration settings for
+            // overlays on the system image. Do not cache this instance so OverlayConfig will not
+            // be present in applications by default.
+            throw new IllegalStateException("Can only be invoked in the root process");
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#getZygoteInstance");
+        try {
+            return new OverlayConfig(null /* rootDirectory */, OverlayScanner::new,
+                    null /* packageProvider */);
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_RRO);
+        }
+    }
+
+    /**
+     * Initializes a singleton instance for use in the system process.
+     * Can only be called once. This instance is cached so future invocations of
+     * {@link #getSystemInstance()} will return the initialized instance.
+     */
+    @NonNull
+    public static OverlayConfig initializeSystemInstance(AndroidPackageProvider packageProvider) {
+        if (Process.myUid() != Process.SYSTEM_UID) {
+            throw new IllegalStateException("Can only be invoked in the system process");
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_RRO, "OverlayConfig#initializeSystemInstance");
+        sInstance = new OverlayConfig(null, null, packageProvider);
+        Trace.traceEnd(Trace.TRACE_TAG_RRO);
+        return sInstance;
+    }
+
+    /**
+     * Retrieves the singleton instance initialized by
+     * {@link #initializeSystemInstance(AndroidPackageProvider)}.
+     */
+    @NonNull
+    public static OverlayConfig getSystemInstance() {
+        if (sInstance == null) {
+            throw new IllegalStateException("System instance not initialized");
+        }
+
+        return sInstance;
+    }
+
+    @VisibleForTesting
+    @Nullable
+    public Configuration getConfiguration(@NonNull String packageName) {
+        return mConfigurations.get(packageName);
+    }
+
+    /**
+     * Returns whether the overlay is enabled by default.
+     * Overlays that are not configured are disabled by default mutable.
+     */
+    public boolean isEnabled(String packageName) {
+        final Configuration config = mConfigurations.get(packageName);
+
+        // STOPSHIP(149499802): Enabling a mutable overlay currently has no effect. Either implement
+        // some behavior for default-enabled, mutable overlays or prevent parsing of the enabled
+        // attribute on overlays that are mutable.
+        if (config != null && config.parsedConfig.mutable) {
+            Log.w(TAG, "Default-enabled configuration for mutable overlay "
+                    + config.parsedConfig.packageName + " has no effect");
+            return OverlayConfigParser.DEFAULT_ENABLED_STATE;
+        }
+
+        return config == null? OverlayConfigParser.DEFAULT_ENABLED_STATE
+                : config.parsedConfig.enabled;
+    }
+
+    /**
+     * Returns whether the overlay is mutable and can have its enabled state changed dynamically.
+     * Overlays that are not configured are mutable.
+     */
+    public boolean isMutable(String packageName) {
+        final Configuration config = mConfigurations.get(packageName);
+        return config == null ? OverlayConfigParser.DEFAULT_MUTABILITY
+                : config.parsedConfig.mutable;
+    }
+
+    /**
+     * Returns an integer corresponding to the priority of the overlay.
+     * When multiple overlays override the same resource, the overlay with the highest priority will
+     * will have its value chosen. Overlays that are not configured have a priority of
+     * {@link Integer#MAX_VALUE}.
+     */
+    public int getPriority(String packageName) {
+        final Configuration config = mConfigurations.get(packageName);
+        return config == null ? DEFAULT_PRIORITY : config.configIndex;
+    }
+
+    @NonNull
+    private ArrayList<Configuration> getSortedOverlays() {
+        final ArrayList<Configuration> sortedOverlays = new ArrayList<>();
+        for (int i = 0, n = mConfigurations.size(); i < n; i++) {
+            sortedOverlays.add(mConfigurations.valueAt(i));
+        }
+        sortedOverlays.sort(Comparator.comparingInt(o -> o.configIndex));
+        return sortedOverlays;
+    }
+
+    @NonNull
+    private static ArrayList<ParsedOverlayInfo> getOverlayPackageInfos(
+            @NonNull AndroidPackageProvider packageManager) {
+        final ArrayList<ParsedOverlayInfo> overlays = new ArrayList<>();
+        packageManager.forEachPackage((AndroidPackage p) -> {
+            if (p.getOverlayTarget() != null && p.isSystem()) {
+                overlays.add(new ParsedOverlayInfo(p.getPackageName(), p.getOverlayTarget(),
+                        p.getTargetSdkVersion(), p.isOverlayIsStatic(), p.getOverlayPriority(),
+                        new File(p.getBaseCodePath())));
+            }
+        });
+        return overlays;
+    }
+
+    /** Represents a single call to idmap create-multiple. */
+    @VisibleForTesting
+    public static class IdmapInvocation {
+        public final boolean enforceOverlayable;
+        public final String policy;
+        public final ArrayList<String> overlayPaths = new ArrayList<>();
+
+        IdmapInvocation(boolean enforceOverlayable, @NonNull String policy) {
+            this.enforceOverlayable = enforceOverlayable;
+            this.policy = policy;
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + String.format("{enforceOverlayable=%s, policy=%s"
+                            + ", overlayPaths=[%s]}", enforceOverlayable, policy,
+                    String.join(", ", overlayPaths));
+        }
+    }
+
+    /**
+     * Retrieves a list of immutable framework overlays in order of least precedence to greatest
+     * precedence.
+     */
+    @VisibleForTesting
+    public ArrayList<IdmapInvocation> getImmutableFrameworkOverlayIdmapInvocations() {
+        final ArrayList<IdmapInvocation> idmapInvocations = new ArrayList<>();
+        final ArrayList<Configuration> sortedConfigs = getSortedOverlays();
+        for (int i = 0, n = sortedConfigs.size(); i < n; i++) {
+            final Configuration overlay = sortedConfigs.get(i);
+            if (overlay.parsedConfig.mutable || !overlay.parsedConfig.enabled
+                    || !"android".equals(overlay.parsedConfig.parsedInfo.targetPackageName)) {
+                continue;
+            }
+
+            // Only enforce that overlays targeting packages with overlayable declarations abide by
+            // those declarations if the target sdk of the overlay is at least Q (when overlayable
+            // was introduced).
+            final boolean enforceOverlayable = overlay.parsedConfig.parsedInfo.targetSdkVersion
+                    >= Build.VERSION_CODES.Q;
+
+            // Determine if the idmap for the current overlay can be generated in the last idmap
+            // create-multiple invocation.
+            IdmapInvocation invocation = null;
+            if (!idmapInvocations.isEmpty()) {
+                final IdmapInvocation last = idmapInvocations.get(idmapInvocations.size() - 1);
+                if (last.enforceOverlayable == enforceOverlayable
+                        && last.policy.equals(overlay.parsedConfig.policy)) {
+                    invocation = last;
+                }
+            }
+
+            if (invocation == null) {
+                invocation = new IdmapInvocation(enforceOverlayable, overlay.parsedConfig.policy);
+                idmapInvocations.add(invocation);
+            }
+
+            invocation.overlayPaths.add(overlay.parsedConfig.parsedInfo.path.getAbsolutePath());
+        }
+        return idmapInvocations;
+    }
+
+    /**
+     * Creates idmap files for immutable overlays targeting the framework packages. Currently the
+     * android package is the only preloaded system package. Only the zygote can invoke this method.
+     *
+     * @return the paths of the created idmap files
+     */
+    @NonNull
+    public String[] createImmutableFrameworkIdmapsInZygote() {
+        if (Process.myUid() != Process.ROOT_UID) {
+            throw new IllegalStateException("This method can only be called from the root process");
+        }
+
+        final String targetPath = "/system/framework/framework-res.apk";
+        final ArrayList<String> idmapPaths = new ArrayList<>();
+        final ArrayList<IdmapInvocation> idmapInvocations =
+                getImmutableFrameworkOverlayIdmapInvocations();
+
+        for (int i = 0, n = idmapInvocations.size(); i < n; i++) {
+            final IdmapInvocation invocation = idmapInvocations.get(i);
+            final String[] idmaps = createIdmap(targetPath,
+                    invocation.overlayPaths.toArray(new String[0]),
+                    new String[]{OverlayConfigParser.OverlayPartition.POLICY_PUBLIC,
+                            invocation.policy},
+                    invocation.enforceOverlayable);
+
+            if (idmaps == null) {
+                Log.w(TAG, "'idmap2 create-multiple' failed: no mutable=\"false\" overlays"
+                        + " targeting \"android\" will be loaded");
+                return new String[0];
+            }
+
+            idmapPaths.addAll(Arrays.asList(idmaps));
+        }
+
+        return idmapPaths.toArray(new String[0]);
+    }
+
+    /**
+     * For each overlay APK, this creates the idmap file that allows the overlay to override the
+     * target package.
+     *
+     * @return the paths of the created idmap
+     */
+    private static native String[] createIdmap(@NonNull String targetPath,
+            @NonNull String[] overlayPath, @NonNull String[] policies, boolean enforceOverlayable);
+}
diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java
new file mode 100644
index 0000000..139607f
--- /dev/null
+++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java
@@ -0,0 +1,391 @@
+/*
+ * 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.internal.content.om;
+
+import static com.android.internal.content.om.OverlayConfig.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackagePartitions;
+import android.content.pm.PackagePartitions.SystemPartition;
+import android.os.FileUtils;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Responsible for parsing configurations of Runtime Resource Overlays that control mutability,
+ * default enable state, and priority. To configure an overlay, create or modify the file located
+ * at {@code partition}/overlay/config/config.xml where {@code partition} is the partition of the
+ * overlay to be configured. In order to be configured, an overlay must reside in the overlay
+ * directory of the partition in which the overlay is configured.
+ *
+ * @see #parseOverlay(File, XmlPullParser, OverlayScanner, ParsingContext)
+ * @see #parseMerge(File, XmlPullParser, OverlayScanner, ParsingContext)
+ **/
+final class OverlayConfigParser {
+
+    // Default values for overlay configurations.
+    static final boolean DEFAULT_ENABLED_STATE = false;
+    static final boolean DEFAULT_MUTABILITY = true;
+
+    // Maximum recursive depth of processing merge tags.
+    private static final int MAXIMUM_MERGE_DEPTH = 5;
+
+    // The subdirectory within a partition's overlay directory that contains the configuration files
+    // for the partition.
+    private static final String CONFIG_DIRECTORY = "config";
+
+    /**
+     * The name of the configuration file to parse for overlay configurations. This class does not
+     * scan for overlay configuration files within the {@link #CONFIG_DIRECTORY}; rather, other
+     * files can be included at a particular position within this file using the <merge> tag.
+     *
+     * @see #parseMerge(File, XmlPullParser, OverlayScanner, ParsingContext)
+     */
+    private static final String CONFIG_DEFAULT_FILENAME = CONFIG_DIRECTORY + "/config.xml";
+
+    /** Represents the configurations of a particular overlay. */
+    public static class ParsedConfiguration {
+        @NonNull
+        public final String packageName;
+
+        /** Whether or not the overlay is enabled by default. */
+        public final boolean enabled;
+
+        /**
+         * Whether or not the overlay is mutable and can have its enabled state changed dynamically
+         * using the {@code OverlayManagerService}.
+         **/
+        public final boolean mutable;
+
+        /** The policy granted to overlays on the partition in which the overlay is located. */
+        @NonNull
+        public final String policy;
+
+        /** Information extracted from the manifest of the overlay. */
+        @NonNull
+        public final ParsedOverlayInfo parsedInfo;
+
+        ParsedConfiguration(@NonNull String packageName, boolean enabled, boolean mutable,
+                @NonNull String policy, @NonNull ParsedOverlayInfo parsedInfo) {
+            this.packageName = packageName;
+            this.enabled = enabled;
+            this.mutable = mutable;
+            this.policy = policy;
+            this.parsedInfo = parsedInfo;
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + String.format("{packageName=%s, enabled=%s"
+                            + ", mutable=%s, policy=%s, parsedInfo=%s}", packageName, enabled,
+                    mutable, policy, parsedInfo);
+        }
+    }
+
+    static class OverlayPartition extends SystemPartition {
+        // Policies passed to idmap2 during idmap creation.
+        // Keep partition policy constants in sync with f/b/cmds/idmap2/include/idmap2/Policies.h.
+        static final String POLICY_ODM = "odm";
+        static final String POLICY_OEM = "oem";
+        static final String POLICY_PRODUCT = "product";
+        static final String POLICY_PUBLIC = "public";
+        static final String POLICY_SYSTEM = "system";
+        static final String POLICY_VENDOR = "vendor";
+
+        @NonNull
+        public final String policy;
+
+        OverlayPartition(@NonNull SystemPartition partition) {
+            super(partition);
+            this.policy = policyForPartition(partition);
+        }
+
+        /**
+         * Creates a partition containing the same folders as the original partition but with a
+         * different root folder.
+         */
+        OverlayPartition(@NonNull File folder, @NonNull SystemPartition original) {
+            super(folder, original);
+            this.policy = policyForPartition(original);
+        }
+
+        private static String policyForPartition(SystemPartition partition) {
+            switch (partition.type) {
+                case PackagePartitions.PARTITION_SYSTEM:
+                case PackagePartitions.PARTITION_SYSTEM_EXT:
+                    return POLICY_SYSTEM;
+                case PackagePartitions.PARTITION_VENDOR:
+                    return POLICY_VENDOR;
+                case PackagePartitions.PARTITION_ODM:
+                    return POLICY_ODM;
+                case PackagePartitions.PARTITION_OEM:
+                    return POLICY_OEM;
+                case PackagePartitions.PARTITION_PRODUCT:
+                    return POLICY_PRODUCT;
+                default:
+                    throw new IllegalStateException("Unable to determine policy for "
+                            + partition.folder);
+            }
+        }
+    }
+
+    /** This class holds state related to parsing the configurations of a partition. */
+    private static class ParsingContext {
+        // The overlay directory of the partition
+        private final OverlayPartition mPartition;
+
+        // The ordered list of configured overlays
+        private final ArrayList<ParsedConfiguration> mOrderedConfigurations = new ArrayList<>();
+
+        // The packages configured in the partition
+        private final ArraySet<String> mConfiguredOverlays = new ArraySet<>();
+
+        // Whether an mutable overlay has been configured in the partition
+        private boolean mFoundMutableOverlay;
+
+        // The current recursive depth of merging configuration files
+        private int mMergeDepth;
+
+        private ParsingContext(OverlayPartition partition) {
+            mPartition = partition;
+        }
+    }
+
+    /**
+     * Retrieves overlays configured within the partition in increasing priority order.
+     *
+     * If {@code scanner} is null, then the {@link ParsedConfiguration#parsedInfo} fields of the
+     * added configured overlays will be null and the parsing logic will not assert that the
+     * configured overlays exist within the partition.
+     *
+     * @return list of configured overlays if configuration file exists; otherwise, null
+     */
+    @Nullable
+    static ArrayList<ParsedConfiguration> getConfigurations(
+            @NonNull OverlayPartition partition, @Nullable OverlayScanner scanner) {
+        if (partition.getOverlayFolder() == null) {
+            return null;
+        }
+
+        if (scanner != null) {
+            scanner.scanDir(partition.getOverlayFolder());
+        }
+
+        final File configFile = new File(partition.getOverlayFolder(), CONFIG_DEFAULT_FILENAME);
+        if (!configFile.exists()) {
+            return null;
+        }
+
+        final ParsingContext parsingContext = new ParsingContext(partition);
+        readConfigFile(configFile, scanner, parsingContext);
+        return parsingContext.mOrderedConfigurations;
+    }
+
+    private static void readConfigFile(@NonNull File configFile, @Nullable OverlayScanner scanner,
+            @NonNull ParsingContext parsingContext) {
+        FileReader configReader;
+        try {
+            configReader = new FileReader(configFile);
+        } catch (FileNotFoundException e) {
+            Log.w(TAG, "Couldn't find or open overlay configuration file " + configFile);
+            return;
+        }
+
+        try {
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(configReader);
+            XmlUtils.beginDocument(parser, "config");
+
+            int depth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, depth)) {
+                final String name = parser.getName();
+                switch (name) {
+                    case "merge":
+                        parseMerge(configFile, parser, scanner, parsingContext);
+                        break;
+                    case "overlay":
+                        parseOverlay(configFile, parser, scanner, parsingContext);
+                        break;
+                    default:
+                        Log.w(TAG, String.format("Tag %s is unknown in %s at %s",
+                                name, configFile, parser.getPositionDescription()));
+                        break;
+                }
+            }
+        } catch (XmlPullParserException | IOException e) {
+            Log.w(TAG, "Got exception parsing overlay configuration.", e);
+        } finally {
+            IoUtils.closeQuietly(configReader);
+        }
+    }
+
+    /**
+     * Parses a <merge> tag within an overlay configuration file.
+     *
+     * Merge tags allow for other configuration files to be "merged" at the current parsing
+     * position into the current configuration file being parsed. The {@code path} attribute of the
+     * tag represents the path of the file to merge relative to the directory containing overlay
+     * configuration files.
+     */
+    private static void parseMerge(@NonNull File configFile, @NonNull XmlPullParser parser,
+            @Nullable OverlayScanner scanner, @NonNull ParsingContext parsingContext) {
+        final String path = parser.getAttributeValue(null, "path");
+        if (path == null) {
+            throw new IllegalStateException(String.format("<merge> without path in %s at %s"
+                    + configFile, parser.getPositionDescription()));
+        }
+
+        if (path.startsWith("/")) {
+            throw new IllegalStateException(String.format(
+                    "Path %s must be relative to the directory containing overlay configurations "
+                            + " files in %s at %s ", path, configFile,
+                    parser.getPositionDescription()));
+        }
+
+        if (parsingContext.mMergeDepth++ == MAXIMUM_MERGE_DEPTH) {
+            throw new IllegalStateException(String.format(
+                    "Maximum <merge> depth exceeded in %s at %s", configFile,
+                    parser.getPositionDescription()));
+        }
+
+        final File configDirectory;
+        final File includedConfigFile;
+        try {
+            configDirectory = new File(parsingContext.mPartition.getOverlayFolder(),
+                    CONFIG_DIRECTORY).getCanonicalFile();
+            includedConfigFile = new File(configDirectory, path).getCanonicalFile();
+        } catch (IOException e) {
+            throw new IllegalStateException(
+                    String.format("Couldn't find or open merged configuration file %s in %s at %s",
+                            path, configFile, parser.getPositionDescription()), e);
+        }
+
+        if (!includedConfigFile.exists()) {
+            throw new IllegalStateException(
+                    String.format("Merged configuration file %s does not exist in %s at %s",
+                            path, configFile, parser.getPositionDescription()));
+        }
+
+        if (!FileUtils.contains(configDirectory, includedConfigFile)) {
+            throw new IllegalStateException(
+                    String.format(
+                            "Merged file %s outside of configuration directory in %s at %s",
+                            includedConfigFile.getAbsolutePath(), includedConfigFile,
+                            parser.getPositionDescription()));
+        }
+
+        readConfigFile(includedConfigFile, scanner, parsingContext);
+        parsingContext.mMergeDepth--;
+    }
+
+    /**
+     * Parses an <overlay> tag within an overlay configuration file.
+     *
+     * Requires a {@code package} attribute that indicates which package is being configured.
+     * The optional {@code enabled} attribute controls whether or not the overlay is enabled by
+     * default (default is false). The optional {@code mutable} attribute controls whether or
+     * not the overlay is mutable and can have its enabled state changed at runtime (default is
+     * true).
+     *
+     * The order in which overlays that override the same resources are configured matters. An
+     * overlay will have a greater priority than overlays with configurations preceding its own
+     * configuration.
+     *
+     * Configurations of immutable overlays must precede configurations of mutable overlays.
+     * An overlay cannot be configured in multiple locations. All configured overlay must exist
+     * within the partition of the configuration file. An overlay cannot be configured multiple
+     * times in a single partition.
+     *
+     * Overlays not listed within a configuration file will be mutable and disabled by default. The
+     * order of non-configured overlays when enabled by the OverlayManagerService is undefined.
+     */
+    private static void parseOverlay(@NonNull File configFile, @NonNull XmlPullParser parser,
+            @Nullable OverlayScanner scanner, @NonNull ParsingContext parsingContext) {
+        final String packageName = parser.getAttributeValue(null, "package");
+        if (packageName == null) {
+            throw new IllegalStateException(String.format("\"<overlay> without package in %s at %s",
+                    configFile, parser.getPositionDescription()));
+        }
+
+        // Ensure the overlay being configured is present in the partition during zygote
+        // initialization.
+        ParsedOverlayInfo info = null;
+        if (scanner != null) {
+            info = scanner.getParsedInfo(packageName);
+            if (info == null|| !parsingContext.mPartition.containsOverlay(info.path)) {
+                throw new IllegalStateException(
+                        String.format("overlay %s not present in partition %s in %s at %s",
+                                packageName, parsingContext.mPartition.getOverlayFolder(),
+                                configFile, parser.getPositionDescription()));
+            }
+        }
+
+        if (parsingContext.mConfiguredOverlays.contains(packageName)) {
+            throw new IllegalStateException(
+                    String.format("overlay %s configured multiple times in a single partition"
+                                    + " in %s at %s", packageName, configFile,
+                            parser.getPositionDescription()));
+        }
+
+        boolean isEnabled = DEFAULT_ENABLED_STATE;
+        final String enabled = parser.getAttributeValue(null, "enabled");
+        if (enabled != null) {
+            isEnabled = !"false".equals(enabled);
+        }
+
+        boolean isMutable = DEFAULT_MUTABILITY;
+        final String mutable = parser.getAttributeValue(null, "mutable");
+        if (mutable != null) {
+            isMutable = !"false".equals(mutable);
+            if (!isMutable && parsingContext.mFoundMutableOverlay) {
+                throw new IllegalStateException(String.format(
+                        "immutable overlays must precede mutable overlays:"
+                                + " found in %s at %s",
+                        configFile, parser.getPositionDescription()));
+            }
+        }
+
+        if (isMutable) {
+            parsingContext.mFoundMutableOverlay = true;
+        } else if (!isEnabled) {
+            // Default disabled, immutable overlays may be a misconfiguration of the system so warn
+            // developers.
+            Log.w(TAG, "found default-disabled immutable overlay " + packageName);
+        }
+
+        final ParsedConfiguration Config = new ParsedConfiguration(packageName, isEnabled,
+                isMutable, parsingContext.mPartition.policy, info);
+        parsingContext.mConfiguredOverlays.add(packageName);
+        parsingContext.mOrderedConfigurations.add(Config);
+    }
+}
diff --git a/core/java/com/android/internal/content/om/OverlayScanner.java b/core/java/com/android/internal/content/om/OverlayScanner.java
new file mode 100644
index 0000000..a85cf56
--- /dev/null
+++ b/core/java/com/android/internal/content/om/OverlayScanner.java
@@ -0,0 +1,138 @@
+/*
+ * 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.internal.content.om;
+
+import static com.android.internal.content.om.OverlayConfig.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * This class scans a directory containing overlay APKs and extracts information from the overlay
+ * manifests by parsing the overlay manifests.
+ */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class OverlayScanner {
+
+    /** Represents information parsed from the manifest of an overlay. */
+    public static class ParsedOverlayInfo {
+        public final String packageName;
+        public final String targetPackageName;
+        public final int targetSdkVersion;
+        public final boolean isStatic;
+        public final int priority;
+        public final File path;
+
+        public ParsedOverlayInfo(String packageName, String targetPackageName,
+                int targetSdkVersion, boolean isStatic, int priority, File path) {
+            this.packageName = packageName;
+            this.targetPackageName = targetPackageName;
+            this.targetSdkVersion = targetSdkVersion;
+            this.isStatic = isStatic;
+            this.priority = priority;
+            this.path = path;
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + String.format("{packageName=%s"
+                            + ", targetPackageName=%s, targetSdkVersion=%s, isStatic=%s"
+                            + ", priority=%s, path=%s}",
+                    packageName, targetPackageName, targetSdkVersion, isStatic, priority, path);
+        }
+    }
+
+    /**
+     * A map of overlay package name to the parsed manifest information of the latest version of
+     * the overlay.
+     */
+    private final ArrayMap<String, ParsedOverlayInfo> mParsedOverlayInfos = new ArrayMap<>();
+
+    /** Retrieves information parsed from the overlay with the package name. */
+    @Nullable
+    public final ParsedOverlayInfo getParsedInfo(String packageName) {
+        return mParsedOverlayInfos.get(packageName);
+    }
+
+    /** Retrieves all of the scanned overlays. */
+    @NonNull
+    final Collection<ParsedOverlayInfo> getAllParsedInfos() {
+        return mParsedOverlayInfos.values();
+    }
+
+    /**
+     * Recursively searches the directory for overlay APKs. If an overlay is found with the same
+     * package name as a previously scanned overlay, the info of the new overlay will replace the
+     * info of the previously scanned overlay.
+     */
+    public void scanDir(File partitionOverlayDir) {
+        if (!partitionOverlayDir.exists() || !partitionOverlayDir.isDirectory()) {
+            return;
+        }
+
+        if (!partitionOverlayDir.canRead()) {
+            Log.w(TAG, "Directory " + partitionOverlayDir + " cannot be read");
+            return;
+        }
+
+        final File[] files = partitionOverlayDir.listFiles();
+        if (files == null) {
+            return;
+        }
+
+        for (int i = 0; i < files.length; i++) {
+            final File f = files[i];
+            if (f.isDirectory()) {
+                scanDir(f);
+            }
+
+            if (!f.isFile() || !f.getPath().endsWith(".apk")) {
+                continue;
+            }
+
+            final ParsedOverlayInfo info = parseOverlayManifest(f);
+            if (info == null) {
+                continue;
+            }
+
+            mParsedOverlayInfos.put(info.packageName, info);
+        }
+    }
+
+    /** Extracts information about the overlay from its manifest. */
+    @VisibleForTesting
+    public ParsedOverlayInfo parseOverlayManifest(File overlayApk) {
+        try {
+            final PackageParser.ApkLite apkLite = PackageParser.parseApkLite(overlayApk, 0);
+            return apkLite.targetPackageName == null ? null :
+                    new ParsedOverlayInfo(apkLite.packageName, apkLite.targetPackageName,
+                            apkLite.targetSdkVersion, apkLite.overlayIsStatic,
+                            apkLite.overlayPriority, new File(apkLite.codePath));
+        } catch (PackageParser.PackageParserException e) {
+            Log.w(TAG, "Got exception loading overlay.", e);
+            return null;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java b/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java
new file mode 100644
index 0000000..26f81d9
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelCpuUidBpfMapReader.java
@@ -0,0 +1,202 @@
+/*
+ * 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.internal.os;
+
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Reads cpu time bpf maps.
+ *
+ * It is implemented as singletons for each separate set of per-UID times. Get___Instance() method
+ * returns the corresponding reader instance. In order to prevent frequent GC, it reuses the same
+ * SparseArray to store data read from BPF maps.
+ *
+ * A KernelCpuUidBpfMapReader instance keeps an error counter. When the number of read errors within
+ * that instance accumulates to 5, this instance will reject all further read requests.
+ *
+ * Data fetched within last 500ms is considered fresh, since the reading lifecycle can take up to
+ * 25ms. KernelCpuUidBpfMapReader always tries to use cache if it is fresh and valid, but it can
+ * be disabled through a parameter.
+ *
+ * A KernelCpuUidBpfMapReader instance is thread-safe. It acquires a write lock when reading the bpf
+ * map, releases it right after, then acquires a read lock before returning a BpfMapIterator. Caller
+ * is responsible for closing BpfMapIterator (also auto-closable) after reading, otherwise deadlock
+ * will occur.
+ */
+public abstract class KernelCpuUidBpfMapReader {
+    private static final int ERROR_THRESHOLD = 5;
+    private static final long FRESHNESS_MS = 500L;
+
+    private static final KernelCpuUidBpfMapReader FREQ_TIME_READER =
+        new KernelCpuUidFreqTimeBpfMapReader();
+
+    private static final KernelCpuUidBpfMapReader ACTIVE_TIME_READER =
+        new KernelCpuUidActiveTimeBpfMapReader();
+
+    private static final KernelCpuUidBpfMapReader CLUSTER_TIME_READER =
+        new KernelCpuUidClusterTimeBpfMapReader();
+
+    static KernelCpuUidBpfMapReader getFreqTimeReaderInstance() {
+        return FREQ_TIME_READER;
+    }
+
+    static KernelCpuUidBpfMapReader getActiveTimeReaderInstance() {
+        return ACTIVE_TIME_READER;
+    }
+
+    static KernelCpuUidBpfMapReader getClusterTimeReaderInstance() {
+        return CLUSTER_TIME_READER;
+    }
+
+    final String mTag = this.getClass().getSimpleName();
+    private int mErrors = 0;
+    private boolean mTracking = false;
+    protected SparseArray<long[]> mData = new SparseArray<>();
+    private long mLastReadTime = 0;
+    protected final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
+    protected final ReentrantReadWriteLock.ReadLock mReadLock = mLock.readLock();
+    protected final ReentrantReadWriteLock.WriteLock mWriteLock = mLock.writeLock();
+
+    public native boolean startTrackingBpfTimes();
+
+    protected abstract boolean readBpfData();
+
+    /**
+     * Returns an array of metadata used to inform the caller of 1) the size of array required by
+     * getNextUid and 2) how to interpret the raw data copied to that array.
+     */
+    public abstract long[] getDataDimensions();
+
+    public void removeUidsInRange(int startUid, int endUid) {
+        if (mErrors > ERROR_THRESHOLD) {
+            return;
+        }
+        mWriteLock.lock();
+        int firstIndex = mData.indexOfKey(startUid);
+        int lastIndex = mData.indexOfKey(endUid);
+        mData.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+        mWriteLock.unlock();
+    }
+
+    public BpfMapIterator open() {
+        return open(false);
+    }
+
+    public BpfMapIterator open(boolean ignoreCache) {
+        if (mErrors > ERROR_THRESHOLD) {
+            return null;
+        }
+        if (!mTracking && !startTrackingBpfTimes()) {
+            Slog.w(mTag, "Failed to start tracking");
+            mErrors++;
+            return null;
+        }
+        if (ignoreCache) {
+            mWriteLock.lock();
+        } else {
+            mReadLock.lock();
+            if (dataValid()) {
+                return new BpfMapIterator();
+            }
+            mReadLock.unlock();
+            mWriteLock.lock();
+            if (dataValid()) {
+                mReadLock.lock();
+                mWriteLock.unlock();
+                return new BpfMapIterator();
+            }
+        }
+        if (readBpfData()) {
+            mLastReadTime = SystemClock.elapsedRealtime();
+            mReadLock.lock();
+            mWriteLock.unlock();
+            return new BpfMapIterator();
+        }
+
+        mWriteLock.unlock();
+        mErrors++;
+        Slog.w(mTag, "Failed to read bpf times");
+        return null;
+    }
+
+    private boolean dataValid() {
+        return mData.size() > 0 && (SystemClock.elapsedRealtime() - mLastReadTime < FRESHNESS_MS);
+    }
+
+    public class BpfMapIterator implements AutoCloseable {
+        private int mPos;
+
+        public BpfMapIterator() {
+        };
+
+        public boolean getNextUid(long[] buf) {
+            if (mPos >= mData.size()) {
+                return false;
+            }
+            buf[0] = mData.keyAt(mPos);
+            System.arraycopy(mData.valueAt(mPos), 0, buf, 1, mData.valueAt(mPos).length);
+            mPos++;
+            return true;
+        }
+
+        public void close() {
+            mReadLock.unlock();
+        }
+    }
+
+    public static class KernelCpuUidFreqTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+        private final native boolean removeUidRange(int startUid, int endUid);
+
+        @Override
+        protected final native boolean readBpfData();
+
+        @Override
+        public final native long[] getDataDimensions();
+
+        @Override
+        public void removeUidsInRange(int startUid, int endUid) {
+            mWriteLock.lock();
+            super.removeUidsInRange(startUid, endUid);
+            removeUidRange(startUid, endUid);
+            mWriteLock.unlock();
+        }
+    }
+
+    public static class KernelCpuUidActiveTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+        @Override
+        protected final native boolean readBpfData();
+
+        @Override
+        public final native long[] getDataDimensions();
+    }
+
+    public static class KernelCpuUidClusterTimeBpfMapReader extends KernelCpuUidBpfMapReader {
+
+        @Override
+        protected final native boolean readBpfData();
+
+        @Override
+        public final native long[] getDataDimensions();
+    }
+}
diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
index f1eb2fb..f7fad2c 100644
--- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.KernelCpuProcStringReader.ProcFileIterator;
+import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator;
 
 import java.io.BufferedReader;
 import java.io.FileWriter;
@@ -57,6 +58,8 @@
     final SparseArray<T> mLastTimes = new SparseArray<>();
     final KernelCpuProcStringReader mReader;
     final boolean mThrottle;
+    protected boolean mBpfTimesAvailable;
+    final KernelCpuUidBpfMapReader mBpfReader;
     private long mMinTimeBetweenRead = DEFAULT_MIN_TIME_BETWEEN_READ;
     private long mLastReadTimeMs = 0;
 
@@ -73,9 +76,15 @@
         void onUidCpuTime(int uid, T time);
     }
 
-    KernelCpuUidTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
+    KernelCpuUidTimeReader(KernelCpuProcStringReader reader, @Nullable KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
         mReader = reader;
         mThrottle = throttle;
+        mBpfReader = bpfReader;
+        mBpfTimesAvailable = (mBpfReader != null);
+    }
+
+    KernelCpuUidTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
+        this(reader, null, throttle);
     }
 
     /**
@@ -151,9 +160,13 @@
         }
         mLastTimes.put(startUid, null);
         mLastTimes.put(endUid, null);
-        final int firstIndex = mLastTimes.indexOfKey(startUid);
-        final int lastIndex = mLastTimes.indexOfKey(endUid);
+        int firstIndex = mLastTimes.indexOfKey(startUid);
+        int lastIndex = mLastTimes.indexOfKey(endUid);
         mLastTimes.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
+
+        if (mBpfTimesAvailable) {
+            mBpfReader.removeUidsInRange(startUid, endUid);
+        }
     }
 
     /**
@@ -323,13 +336,13 @@
 
         public KernelCpuUidFreqTimeReader(boolean throttle) {
             this(UID_TIMES_PROC_FILE, KernelCpuProcStringReader.getFreqTimeReaderInstance(),
-                    throttle);
+                 KernelCpuUidBpfMapReader.getFreqTimeReaderInstance(), throttle);
         }
 
         @VisibleForTesting
         public KernelCpuUidFreqTimeReader(String procFile, KernelCpuProcStringReader reader,
-                boolean throttle) {
-            super(reader, throttle);
+                KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+            super(reader, bpfReader, throttle);
             mProcFilePath = Paths.get(procFile);
         }
 
@@ -370,19 +383,24 @@
             if (!mAllUidTimesAvailable) {
                 return null;
             }
-            final int oldMask = StrictMode.allowThreadDiskReadsMask();
-            try (BufferedReader reader = Files.newBufferedReader(mProcFilePath)) {
-                if (readFreqs(reader.readLine()) == null) {
+            if (mBpfTimesAvailable) {
+                readFreqsThroughBpf();
+            }
+            if (mCpuFreqs == null) {
+                final int oldMask = StrictMode.allowThreadDiskReadsMask();
+                try (BufferedReader reader = Files.newBufferedReader(mProcFilePath)) {
+                    if (readFreqs(reader.readLine()) == null) {
+                        return null;
+                    }
+                } catch (IOException e) {
+                    if (++mErrors >= MAX_ERROR_COUNT) {
+                        mAllUidTimesAvailable = false;
+                    }
+                    Slog.e(mTag, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
                     return null;
+                } finally {
+                    StrictMode.setThreadPolicyMask(oldMask);
                 }
-            } catch (IOException e) {
-                if (++mErrors >= MAX_ERROR_COUNT) {
-                    mAllUidTimesAvailable = false;
-                }
-                Slog.e(mTag, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
-                return null;
-            } finally {
-                StrictMode.setThreadPolicyMask(oldMask);
             }
             // Check if the freqs in the proc file correspond to per-cluster freqs.
             final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
@@ -402,6 +420,21 @@
             return mCpuFreqs;
         }
 
+        private long[] readFreqsThroughBpf() {
+            if (!mBpfTimesAvailable || mBpfReader == null) {
+                return null;
+            }
+            mCpuFreqs = mBpfReader.getDataDimensions();
+            if (mCpuFreqs == null) {
+                return null;
+            }
+            mFreqCount = mCpuFreqs.length;
+            mCurTimes = new long[mFreqCount];
+            mDeltaTimes = new long[mFreqCount];
+            mBuffer = new long[mFreqCount + 1];
+            return mCpuFreqs;
+        }
+
         private long[] readFreqs(String line) {
             if (line == null || line.trim().isEmpty()) {
                 return null;
@@ -422,8 +455,45 @@
             return mCpuFreqs;
         }
 
+        private void processUidDelta(@Nullable Callback<long[]> cb) {
+            final int uid = (int) mBuffer[0];
+            long[] lastTimes = mLastTimes.get(uid);
+            if (lastTimes == null) {
+                lastTimes = new long[mFreqCount];
+                mLastTimes.put(uid, lastTimes);
+            }
+            copyToCurTimes();
+            boolean notify = false;
+            boolean valid = true;
+            for (int i = 0; i < mFreqCount; i++) {
+                // Unit is 10ms.
+                mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
+                if (mDeltaTimes[i] < 0) {
+                    Slog.e(mTag, "Negative delta from freq time proc: " + mDeltaTimes[i]);
+                    valid = false;
+                }
+                notify |= mDeltaTimes[i] > 0;
+            }
+            if (notify && valid) {
+                System.arraycopy(mCurTimes, 0, lastTimes, 0, mFreqCount);
+                if (cb != null) {
+                    cb.onUidCpuTime(uid, mDeltaTimes);
+                }
+            }
+        }
+
         @Override
         void readDeltaImpl(@Nullable Callback<long[]> cb) {
+            if (mBpfTimesAvailable) {
+                try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+                    if (checkPrecondition(iter)) {
+                        while (iter.getNextUid(mBuffer)) {
+                            processUidDelta(cb);
+                        }
+                        return;
+                    }
+                }
+            }
             try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                 if (!checkPrecondition(iter)) {
                     return;
@@ -434,36 +504,24 @@
                         Slog.wtf(mTag, "Invalid line: " + buf.toString());
                         continue;
                     }
-                    final int uid = (int) mBuffer[0];
-                    long[] lastTimes = mLastTimes.get(uid);
-                    if (lastTimes == null) {
-                        lastTimes = new long[mFreqCount];
-                        mLastTimes.put(uid, lastTimes);
-                    }
-                    copyToCurTimes();
-                    boolean notify = false;
-                    boolean valid = true;
-                    for (int i = 0; i < mFreqCount; i++) {
-                        // Unit is 10ms.
-                        mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
-                        if (mDeltaTimes[i] < 0) {
-                            Slog.e(mTag, "Negative delta from freq time proc: " + mDeltaTimes[i]);
-                            valid = false;
-                        }
-                        notify |= mDeltaTimes[i] > 0;
-                    }
-                    if (notify && valid) {
-                        System.arraycopy(mCurTimes, 0, lastTimes, 0, mFreqCount);
-                        if (cb != null) {
-                            cb.onUidCpuTime(uid, mDeltaTimes);
-                        }
-                    }
+                    processUidDelta(cb);
                 }
             }
         }
 
         @Override
         void readAbsoluteImpl(Callback<long[]> cb) {
+            if (mBpfTimesAvailable) {
+                try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+                    if (checkPrecondition(iter)) {
+                        while (iter.getNextUid(mBuffer)) {
+                            copyToCurTimes();
+                            cb.onUidCpuTime((int) mBuffer[0], mCurTimes);
+                        }
+                        return;
+                    }
+                }
+            }
             try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                 if (!checkPrecondition(iter)) {
                     return;
@@ -481,11 +539,24 @@
         }
 
         private void copyToCurTimes() {
+            long factor = mBpfTimesAvailable ? 1 : 10;
             for (int i = 0; i < mFreqCount; i++) {
-                mCurTimes[i] = mBuffer[i + 1] * 10;
+                mCurTimes[i] = mBuffer[i + 1] * factor;
             }
         }
 
+        private boolean checkPrecondition(BpfMapIterator iter) {
+            if (iter == null) {
+                mBpfTimesAvailable = false;
+                return false;
+            }
+            if (mCpuFreqs != null) {
+                return true;
+            }
+            mBpfTimesAvailable = (readFreqsThroughBpf() != null);
+            return mBpfTimesAvailable;
+        }
+
         private boolean checkPrecondition(ProcFileIterator iter) {
             if (iter == null || !iter.hasNextLine()) {
                 // Error logged in KernelCpuProcStringReader.
@@ -544,16 +615,43 @@
         private long[] mBuffer;
 
         public KernelCpuUidActiveTimeReader(boolean throttle) {
-            super(KernelCpuProcStringReader.getActiveTimeReaderInstance(), throttle);
+            super(KernelCpuProcStringReader.getActiveTimeReaderInstance(),
+                  KernelCpuUidBpfMapReader.getActiveTimeReaderInstance(), throttle);
         }
 
         @VisibleForTesting
-        public KernelCpuUidActiveTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
-            super(reader, throttle);
+        public KernelCpuUidActiveTimeReader(KernelCpuProcStringReader reader, KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+            super(reader, bpfReader, throttle);
+        }
+
+        private void processUidDelta(@Nullable Callback<Long> cb) {
+            int uid = (int) mBuffer[0];
+            long cpuActiveTime = sumActiveTime(mBuffer, mBpfTimesAvailable ? 1 : 10);
+            if (cpuActiveTime > 0) {
+                long delta = cpuActiveTime - mLastTimes.get(uid, 0L);
+                if (delta > 0) {
+                    mLastTimes.put(uid, cpuActiveTime);
+                    if (cb != null) {
+                        cb.onUidCpuTime(uid, delta);
+                    }
+                } else if (delta < 0) {
+                    Slog.e(mTag, "Negative delta from active time proc: " + delta);
+                }
+            }
         }
 
         @Override
         void readDeltaImpl(@Nullable Callback<Long> cb) {
+            if (mBpfTimesAvailable) {
+                try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+                    if (checkPrecondition(iter)) {
+                        while (iter.getNextUid(mBuffer)) {
+                            processUidDelta(cb);
+                        }
+                        return;
+                    }
+                }
+            }
             try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                 if (!checkPrecondition(iter)) {
                     return;
@@ -564,25 +662,30 @@
                         Slog.wtf(mTag, "Invalid line: " + buf.toString());
                         continue;
                     }
-                    int uid = (int) mBuffer[0];
-                    long cpuActiveTime = sumActiveTime(mBuffer);
-                    if (cpuActiveTime > 0) {
-                        long delta = cpuActiveTime - mLastTimes.get(uid, 0L);
-                        if (delta > 0) {
-                            mLastTimes.put(uid, cpuActiveTime);
-                            if (cb != null) {
-                                cb.onUidCpuTime(uid, delta);
-                            }
-                        } else if (delta < 0) {
-                            Slog.e(mTag, "Negative delta from active time proc: " + delta);
-                        }
-                    }
+                    processUidDelta(cb);
                 }
             }
         }
 
+        private void processUidAbsolute(@Nullable Callback<Long> cb) {
+            long cpuActiveTime = sumActiveTime(mBuffer, mBpfTimesAvailable ? 1 : 10);
+            if (cpuActiveTime > 0) {
+                cb.onUidCpuTime((int) mBuffer[0], cpuActiveTime);
+            }
+        }
+
         @Override
         void readAbsoluteImpl(Callback<Long> cb) {
+            if (mBpfTimesAvailable) {
+                try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+                    if (checkPrecondition(iter)) {
+                        while (iter.getNextUid(mBuffer)) {
+                            processUidAbsolute(cb);
+                        }
+                        return;
+                    }
+                }
+            }
             try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                 if (!checkPrecondition(iter)) {
                     return;
@@ -593,23 +696,38 @@
                         Slog.wtf(mTag, "Invalid line: " + buf.toString());
                         continue;
                     }
-                    long cpuActiveTime = sumActiveTime(mBuffer);
-                    if (cpuActiveTime > 0) {
-                        cb.onUidCpuTime((int) mBuffer[0], cpuActiveTime);
-                    }
+                    processUidAbsolute(cb);
                 }
             }
         }
 
-        private static long sumActiveTime(long[] times) {
+        private static long sumActiveTime(long[] times, double factor) {
             // UID is stored at times[0].
             double sum = 0;
             for (int i = 1; i < times.length; i++) {
-                sum += (double) times[i] * 10 / i; // Unit is 10ms.
+                sum += (double) times[i] * factor / i; // Unit is 10ms.
             }
             return (long) sum;
         }
 
+        private boolean checkPrecondition(BpfMapIterator iter) {
+            if (iter == null) {
+                mBpfTimesAvailable = false;
+                return false;
+            }
+            if (mCores > 0) {
+                return true;
+            }
+            long[] cores = mBpfReader.getDataDimensions();
+            if (cores == null || cores.length < 1) {
+                mBpfTimesAvailable = false;
+                return false;
+            }
+            mCores = (int) cores[0];
+            mBuffer = new long[mCores + 1];
+            return true;
+        }
+
         private boolean checkPrecondition(ProcFileIterator iter) {
             if (iter == null || !iter.hasNextLine()) {
                 // Error logged in KernelCpuProcStringReader.
@@ -668,16 +786,54 @@
         private long[] mDeltaTime;
 
         public KernelCpuUidClusterTimeReader(boolean throttle) {
-            super(KernelCpuProcStringReader.getClusterTimeReaderInstance(), throttle);
+            super(KernelCpuProcStringReader.getClusterTimeReaderInstance(),
+                  KernelCpuUidBpfMapReader.getClusterTimeReaderInstance(), throttle);
         }
 
         @VisibleForTesting
-        public KernelCpuUidClusterTimeReader(KernelCpuProcStringReader reader, boolean throttle) {
-            super(reader, throttle);
+        public KernelCpuUidClusterTimeReader(KernelCpuProcStringReader reader,
+                                             KernelCpuUidBpfMapReader bpfReader, boolean throttle) {
+            super(reader, bpfReader, throttle);
+        }
+
+        void processUidDelta(@Nullable Callback<long[]> cb) {
+            int uid = (int) mBuffer[0];
+            long[] lastTimes = mLastTimes.get(uid);
+            if (lastTimes == null) {
+                lastTimes = new long[mNumClusters];
+                mLastTimes.put(uid, lastTimes);
+            }
+            sumClusterTime();
+            boolean valid = true;
+            boolean notify = false;
+            for (int i = 0; i < mNumClusters; i++) {
+                mDeltaTime[i] = mCurTime[i] - lastTimes[i];
+                if (mDeltaTime[i] < 0) {
+                    Slog.e(mTag, "Negative delta from cluster time proc: " + mDeltaTime[i]);
+                    valid = false;
+                }
+                notify |= mDeltaTime[i] > 0;
+            }
+            if (notify && valid) {
+                System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
+                if (cb != null) {
+                    cb.onUidCpuTime(uid, mDeltaTime);
+                }
+            }
         }
 
         @Override
         void readDeltaImpl(@Nullable Callback<long[]> cb) {
+            if (mBpfTimesAvailable) {
+                try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+                    if (checkPrecondition(iter)) {
+                        while (iter.getNextUid(mBuffer)) {
+                            processUidDelta(cb);
+                        }
+                        return;
+                    }
+                }
+            }
             try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                 if (!checkPrecondition(iter)) {
                     return;
@@ -688,35 +844,24 @@
                         Slog.wtf(mTag, "Invalid line: " + buf.toString());
                         continue;
                     }
-                    int uid = (int) mBuffer[0];
-                    long[] lastTimes = mLastTimes.get(uid);
-                    if (lastTimes == null) {
-                        lastTimes = new long[mNumClusters];
-                        mLastTimes.put(uid, lastTimes);
-                    }
-                    sumClusterTime();
-                    boolean valid = true;
-                    boolean notify = false;
-                    for (int i = 0; i < mNumClusters; i++) {
-                        mDeltaTime[i] = mCurTime[i] - lastTimes[i];
-                        if (mDeltaTime[i] < 0) {
-                            Slog.e(mTag, "Negative delta from cluster time proc: " + mDeltaTime[i]);
-                            valid = false;
-                        }
-                        notify |= mDeltaTime[i] > 0;
-                    }
-                    if (notify && valid) {
-                        System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
-                        if (cb != null) {
-                            cb.onUidCpuTime(uid, mDeltaTime);
-                        }
-                    }
+                    processUidDelta(cb);
                 }
             }
         }
 
         @Override
         void readAbsoluteImpl(Callback<long[]> cb) {
+            if (mBpfTimesAvailable) {
+                try (BpfMapIterator iter = mBpfReader.open(!mThrottle)) {
+                    if (checkPrecondition(iter)) {
+                        while (iter.getNextUid(mBuffer)) {
+                            sumClusterTime();
+                            cb.onUidCpuTime((int) mBuffer[0], mCurTime);
+                        }
+                        return;
+                    }
+                }
+            }
             try (ProcFileIterator iter = mReader.open(!mThrottle)) {
                 if (!checkPrecondition(iter)) {
                     return;
@@ -734,17 +879,45 @@
         }
 
         private void sumClusterTime() {
+            double factor = mBpfTimesAvailable ? 1 : 10;
             // UID is stored at mBuffer[0].
             int core = 1;
             for (int i = 0; i < mNumClusters; i++) {
                 double sum = 0;
                 for (int j = 1; j <= mCoresOnClusters[i]; j++) {
-                    sum += (double) mBuffer[core++] * 10 / j; // Unit is 10ms.
+                    sum += (double) mBuffer[core++] * factor / j; // Unit is 10ms.
                 }
                 mCurTime[i] = (long) sum;
             }
         }
 
+        private boolean checkPrecondition(BpfMapIterator iter) {
+            if (iter == null) {
+                mBpfTimesAvailable = false;
+                return false;
+            }
+            if (mNumClusters > 0) {
+                return true;
+            }
+            long[] coresOnClusters = mBpfReader.getDataDimensions();
+            if (coresOnClusters == null || coresOnClusters.length < 1) {
+                mBpfTimesAvailable = false;
+                return false;
+            }
+            mNumClusters = coresOnClusters.length;
+            mCoresOnClusters = new int[mNumClusters];
+            int cores = 0;
+            for (int i = 0; i < mNumClusters; i++) {
+                mCoresOnClusters[i] = (int) coresOnClusters[i];
+                cores += mCoresOnClusters[i];
+            }
+            mNumCores = cores;
+            mBuffer = new long[cores + 1];
+            mCurTime = new long[mNumClusters];
+            mDeltaTime = new long[mNumClusters];
+            return true;
+        }
+
         private boolean checkPrecondition(ProcFileIterator iter) {
             if (iter == null || !iter.hasNextLine()) {
                 // Error logged in KernelCpuProcStringReader.
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index 3c43a11..fc0ce6f 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -53,6 +53,8 @@
     private int mReadErrorCounter;
     @GuardedBy("this")
     private boolean mSingleUidCpuTimesAvailable = true;
+    @GuardedBy("this")
+    private boolean mBpfTimesAvailable = true;
     // We use the freq count obtained from /proc/uid_time_in_state to decide how many longs
     // to read from each /proc/uid/<uid>/time_in_state. On the first read, verify if this is
     // correct and if not, set {@link #mSingleUidCpuTimesAvailable} to false. This flag will
@@ -62,6 +64,8 @@
 
     private final Injector mInjector;
 
+    private static final native boolean canReadBpfTimes();
+
     KernelSingleUidTimeReader(int cpuFreqsCount) {
         this(cpuFreqsCount, new Injector());
     }
@@ -83,6 +87,18 @@
             if (!mSingleUidCpuTimesAvailable) {
                 return null;
             }
+            if (mBpfTimesAvailable) {
+                final long[] cpuTimesMs = mInjector.readBpfData(uid);
+                if (cpuTimesMs.length == 0) {
+                    mBpfTimesAvailable = false;
+                } else if (!mCpuFreqsCountVerified && cpuTimesMs.length != mCpuFreqsCount) {
+                    mSingleUidCpuTimesAvailable = false;
+                    return null;
+                } else {
+                    mCpuFreqsCountVerified = true;
+                    return computeDelta(uid, cpuTimesMs);
+                }
+            }
             // Read total cpu times from the proc file.
             final String procFile = new StringBuilder(PROC_FILE_DIR)
                     .append(uid)
@@ -230,6 +246,8 @@
         public byte[] readData(String procFile) throws IOException {
             return Files.readAllBytes(Paths.get(procFile));
         }
+
+        public native long[] readBpfData(int uid);
     }
 
     @VisibleForTesting
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 775368b..24222d3 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -335,7 +335,7 @@
         super(context);
         mLayoutInflater = LayoutInflater.from(context);
         mRenderShadowsInCompositor = Settings.Global.getInt(context.getContentResolver(),
-                DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 0) != 0;
+                DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 1) != 0;
     }
 
     /**
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 578c0cc..3378c07 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -1160,6 +1160,10 @@
             addFeature(PackageManager.FEATURE_INCREMENTAL_DELIVERY, 0);
         }
 
+        if (PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT) {
+            addFeature(PackageManager.FEATURE_APP_ENUMERATION, 0);
+        }
+
         for (String featureName : mUnavailableFeatures) {
             removeFeature(featureName);
         }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 900445c..253ecdc 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -197,8 +197,11 @@
                 "android_content_res_ObbScanner.cpp",
                 "android_content_res_Configuration.cpp",
                 "android_security_Scrypt.cpp",
+                "com_android_internal_content_om_OverlayConfig.cpp",
                 "com_android_internal_os_ClassLoaderFactory.cpp",
                 "com_android_internal_os_FuseAppLoop.cpp",
+                "com_android_internal_os_KernelCpuUidBpfMapReader.cpp",
+                "com_android_internal_os_KernelSingleUidTimeReader.cpp",
                 "com_android_internal_os_Zygote.cpp",
                 "com_android_internal_os_ZygoteInit.cpp",
                 "hwbinder/EphemeralStorage.cpp",
@@ -273,6 +276,7 @@
                 "libdl",
                 "libdl_android",
                 "libstatslog",
+                "libtimeinstate",
                 "server_configurable_flags",
                 "libstatspull",
             ],
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index d790ada..b711186 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -186,8 +186,11 @@
 extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
 extern int register_android_security_Scrypt(JNIEnv *env);
 extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
+extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
 extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
+extern int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env);
+extern int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env);
 extern int register_com_android_internal_os_Zygote(JNIEnv *env);
 extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
 extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
@@ -1497,6 +1500,7 @@
         REG_JNI(register_android_os_MemoryFile),
         REG_JNI(register_android_os_SharedMemory),
         REG_JNI(register_android_os_incremental_IncrementalManager),
+        REG_JNI(register_com_android_internal_content_om_OverlayConfig),
         REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
         REG_JNI(register_com_android_internal_os_Zygote),
         REG_JNI(register_com_android_internal_os_ZygoteInit),
@@ -1558,6 +1562,8 @@
         REG_JNI(register_android_security_Scrypt),
         REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
         REG_JNI(register_com_android_internal_os_FuseAppLoop),
+        REG_JNI(register_com_android_internal_os_KernelCpuUidBpfMapReader),
+        REG_JNI(register_com_android_internal_os_KernelSingleUidTimeReader),
 };
 
 /*
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 30e914d..130322a 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -19,11 +19,10 @@
 #include <utils/Color.h>
 
 #ifdef __ANDROID__ // Layoutlib does not support graphic buffer, parcel or render thread
-#include <binder/Parcel.h>
-#include <renderthread/RenderProxy.h>
 #include <android_runtime/android_graphics_GraphicBuffer.h>
-#include <android_runtime/android_hardware_HardwareBuffer.h>
-#include <private/android/AHardwareBufferHelpers.h>
+#include <binder/Parcel.h>
+#include <dlfcn.h>
+#include <renderthread/RenderProxy.h>
 #endif
 
 #include "core_jni_helpers.h"
@@ -1027,11 +1026,18 @@
     return createBitmap(env, bitmap.release(), getPremulBitmapCreateFlags(false));
 }
 
+#ifdef __ANDROID__ // Layoutlib does not support graphic buffer
+typedef AHardwareBuffer* (*AHB_from_HB)(JNIEnv*, jobject);
+AHB_from_HB AHardwareBuffer_fromHardwareBuffer;
+
+typedef jobject (*AHB_to_HB)(JNIEnv*, AHardwareBuffer*);
+AHB_to_HB AHardwareBuffer_toHardwareBuffer;
+#endif
+
 static jobject Bitmap_wrapHardwareBufferBitmap(JNIEnv* env, jobject, jobject hardwareBuffer,
                                                jlong colorSpacePtr) {
 #ifdef __ANDROID__ // Layoutlib does not support graphic buffer
-    AHardwareBuffer* buffer = android_hardware_HardwareBuffer_getNativeHardwareBuffer(env,
-        hardwareBuffer);
+    AHardwareBuffer* buffer = AHardwareBuffer_fromHardwareBuffer(env, hardwareBuffer);
     sk_sp<Bitmap> bitmap = Bitmap::createFrom(buffer,
                                               GraphicsJNI::getNativeColorSpace(colorSpacePtr));
     if (!bitmap.get()) {
@@ -1057,6 +1063,19 @@
 #endif
 }
 
+static jobject Bitmap_getHardwareBuffer(JNIEnv* env, jobject, jlong bitmapPtr) {
+#ifdef __ANDROID__ // Layoutlib does not support graphic buffer
+    LocalScopedBitmap bitmapHandle(bitmapPtr);
+    LOG_ALWAYS_FATAL_IF(!bitmapHandle->isHardware(),
+            "Hardware config is only supported config in Bitmap_getHardwareBuffer");
+
+    Bitmap& bitmap = bitmapHandle->bitmap();
+    return AHardwareBuffer_toHardwareBuffer(env, bitmap.hardwareBuffer());
+#else
+    return NULL;
+#endif
+}
+
 static jboolean Bitmap_isImmutable(CRITICAL_JNI_PARAMS_COMMA jlong bitmapHandle) {
     LocalScopedBitmap bitmapHolder(bitmapHandle);
     if (!bitmapHolder.valid()) return JNI_FALSE;
@@ -1123,6 +1142,8 @@
         (void*) Bitmap_wrapHardwareBufferBitmap },
     {   "nativeCreateGraphicBufferHandle", "(J)Landroid/graphics/GraphicBuffer;",
         (void*) Bitmap_createGraphicBufferHandle },
+    {   "nativeGetHardwareBuffer", "(J)Landroid/hardware/HardwareBuffer;",
+        (void*) Bitmap_getHardwareBuffer },
     {   "nativeComputeColorSpace",  "(J)Landroid/graphics/ColorSpace;", (void*)Bitmap_computeColorSpace },
     {   "nativeSetColorSpace",      "(JJ)V", (void*)Bitmap_setColorSpace },
     {   "nativeIsSRGB",             "(J)Z", (void*)Bitmap_isSRGB },
@@ -1140,6 +1161,18 @@
     gBitmap_nativePtr = GetFieldIDOrDie(env, gBitmap_class, "mNativePtr", "J");
     gBitmap_constructorMethodID = GetMethodIDOrDie(env, gBitmap_class, "<init>", "(JIIIZ[BLandroid/graphics/NinePatch$InsetStruct;Z)V");
     gBitmap_reinitMethodID = GetMethodIDOrDie(env, gBitmap_class, "reinit", "(IIZ)V");
+
+#ifdef __ANDROID__ // Layoutlib does not support graphic buffer
+    void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
+    AHardwareBuffer_fromHardwareBuffer =
+            (AHB_from_HB)dlsym(handle_, "AHardwareBuffer_fromHardwareBuffer");
+    LOG_ALWAYS_FATAL_IF(AHardwareBuffer_fromHardwareBuffer == nullptr,
+                        "Failed to find required symbol AHardwareBuffer_fromHardwareBuffer!");
+
+    AHardwareBuffer_toHardwareBuffer = (AHB_to_HB)dlsym(handle_, "AHardwareBuffer_toHardwareBuffer");
+    LOG_ALWAYS_FATAL_IF(AHardwareBuffer_toHardwareBuffer == nullptr,
+                        " Failed to find required symbol AHardwareBuffer_toHardwareBuffer!");
+#endif
     return android::RegisterMethodsOrDie(env, "android/graphics/Bitmap", gBitmapMethods,
                                          NELEM(gBitmapMethods));
 }
diff --git a/core/jni/com_android_internal_content_om_OverlayConfig.cpp b/core/jni/com_android_internal_content_om_OverlayConfig.cpp
new file mode 100644
index 0000000..6aa7c10
--- /dev/null
+++ b/core/jni/com_android_internal_content_om_OverlayConfig.cpp
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+#include "core_jni_helpers.h"
+
+#include "android-base/logging.h"
+#include "androidfw/PosixUtils.h"
+
+using ::android::util::ExecuteBinary;
+
+static jclass g_stringClass = nullptr;
+
+static jobjectArray createIdmap(JNIEnv* env, jclass /*clazz*/, jstring targetPath,
+                           jobjectArray overlayPath, jobjectArray policies,
+                           jboolean enforceOverlayable) {
+  if (access("/system/bin/idmap2", X_OK) == -1) {
+    PLOG(WARNING) << "unable to execute idmap2";
+    return nullptr;
+  }
+
+  const char* targetApkPath = env->GetStringUTFChars(targetPath, NULL /* isCopy */);
+  std::vector<std::string> argv{"/system/bin/idmap2",
+    "create-multiple",
+    "--target-apk-path", targetApkPath,
+  };
+  env->ReleaseStringUTFChars(targetPath, targetApkPath);
+
+  // Add the overlays for which to generate idmap files to the idmap arguments.
+  for (size_t i = 0, count = env->GetArrayLength(overlayPath); i < count; ++i) {
+    jstring element = (jstring) env->GetObjectArrayElement(overlayPath, i);
+    const char* overlayApkPath = env->GetStringUTFChars(element, NULL /* isCopy */);
+    argv.emplace_back("--overlay-apk-path");
+    argv.emplace_back(overlayApkPath);
+    env->ReleaseStringUTFChars(element, overlayApkPath);
+  }
+
+  // Add the policies the overlays fulfill to the idmap arguments.
+  for (size_t i = 0, count = env->GetArrayLength(policies); i < count; ++i) {
+    jstring element = (jstring)env->GetObjectArrayElement(policies, i);
+    const char* policy = env->GetStringUTFChars(element, NULL /* isCopy */);
+    argv.emplace_back("--policy");
+    argv.emplace_back(policy);
+    env->ReleaseStringUTFChars(element, policy);
+  }
+
+  if (!enforceOverlayable) {
+    argv.emplace_back("--ignore-overlayable");
+  }
+
+  const auto result = ExecuteBinary(argv);
+  if (!result) {
+      LOG(ERROR) << "failed to execute idmap2";
+      return nullptr;
+  }
+
+  if (result->status != 0) {
+    LOG(ERROR) << "idmap2: " << result->stderr;
+    return nullptr;
+  }
+
+  // Return the paths of the idmaps created or updated during the idmap invocation.
+  std::vector<std::string> idmap_paths;
+  std::istringstream input(result->stdout);
+  std::string path;
+  while (std::getline(input, path)) {
+    idmap_paths.push_back(path);
+  }
+
+  jobjectArray array = env->NewObjectArray(idmap_paths.size(), g_stringClass, nullptr);
+  if (array == nullptr) {
+    return nullptr;
+  }
+  for (size_t i = 0; i < idmap_paths.size(); i++) {
+    const std::string path = idmap_paths[i];
+    jstring java_string = env->NewStringUTF(path.c_str());
+    if (env->ExceptionCheck()) {
+      return nullptr;
+    }
+    env->SetObjectArrayElement(array, i, java_string);
+    env->DeleteLocalRef(java_string);
+  }
+
+  return array;
+}
+
+static const JNINativeMethod g_methods[] = {
+    { "createIdmap",
+      "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;Z)[Ljava/lang/String;",
+      (void *)createIdmap },
+};
+
+static const char* const kOverlayConfigPathName = "com/android/internal/content/om/OverlayConfig";
+
+namespace android {
+
+int register_com_android_internal_content_om_OverlayConfig(JNIEnv* env) {
+  jclass stringClass = FindClassOrDie(env, "java/lang/String");
+  g_stringClass = MakeGlobalRefOrDie(env, stringClass);
+
+  return RegisterMethodsOrDie(env, kOverlayConfigPathName, g_methods, NELEM(g_methods));
+}
+
+} // namespace android
diff --git a/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
new file mode 100644
index 0000000..7c68de5
--- /dev/null
+++ b/core/jni/com_android_internal_os_KernelCpuUidBpfMapReader.cpp
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+#include "core_jni_helpers.h"
+
+#include <sys/sysinfo.h>
+
+#include <android-base/stringprintf.h>
+#include <cputimeinstate.h>
+
+namespace android {
+
+static constexpr uint64_t NSEC_PER_MSEC = 1000000;
+
+static struct {
+    jclass clazz;
+    jmethodID put;
+    jmethodID get;
+} gSparseArrayClassInfo;
+
+static jfieldID gmData;
+
+static jlongArray getUidArray(JNIEnv *env, jobject sparseAr, uint32_t uid, jsize sz) {
+    jlongArray ar = (jlongArray)env->CallObjectMethod(sparseAr, gSparseArrayClassInfo.get, uid);
+    if (!ar) {
+        ar = env->NewLongArray(sz);
+        if (ar == NULL) return ar;
+        env->CallVoidMethod(sparseAr, gSparseArrayClassInfo.put, uid, ar);
+    }
+    return ar;
+}
+
+static void copy2DVecToArray(JNIEnv *env, jlongArray ar, std::vector<std::vector<uint64_t>> &vec) {
+    jsize start = 0;
+    for (auto &subVec : vec) {
+        for (uint32_t i = 0; i < subVec.size(); ++i) subVec[i] /= NSEC_PER_MSEC;
+        env->SetLongArrayRegion(ar, start, subVec.size(),
+                                reinterpret_cast<const jlong *>(subVec.data()));
+        start += subVec.size();
+    }
+}
+
+static jboolean KernelCpuUidFreqTimeBpfMapReader_removeUidRange(JNIEnv *env, jclass, jint startUid,
+                                                                jint endUid) {
+    for (uint32_t uid = startUid; uid <= endUid; ++uid) {
+        if (!android::bpf::clearUidTimes(uid)) return false;
+    }
+    return true;
+}
+
+static jboolean KernelCpuUidFreqTimeBpfMapReader_readBpfData(JNIEnv *env, jobject thiz) {
+    static uint64_t lastUpdate = 0;
+    uint64_t newLastUpdate = lastUpdate;
+    auto sparseAr = env->GetObjectField(thiz, gmData);
+    if (sparseAr == NULL) return false;
+    auto data = android::bpf::getUidsUpdatedCpuFreqTimes(&newLastUpdate);
+    if (!data.has_value()) return false;
+
+    jsize s = 0;
+    for (auto &[uid, times] : *data) {
+        if (s == 0) {
+            for (const auto &subVec : times) s += subVec.size();
+        }
+        jlongArray ar = getUidArray(env, sparseAr, uid, s);
+        if (ar == NULL) return false;
+        copy2DVecToArray(env, ar, times);
+    }
+    lastUpdate = newLastUpdate;
+    return true;
+}
+
+static jlongArray KernelCpuUidFreqTimeBpfMapReader_getDataDimensions(JNIEnv *env, jobject) {
+    auto freqs = android::bpf::getCpuFreqs();
+    if (!freqs) return NULL;
+
+    std::vector<uint64_t> allFreqs;
+    for (const auto &vec : *freqs) std::copy(vec.begin(), vec.end(), std::back_inserter(allFreqs));
+
+    auto ar = env->NewLongArray(allFreqs.size());
+    if (ar != NULL) {
+        env->SetLongArrayRegion(ar, 0, allFreqs.size(),
+                                reinterpret_cast<const jlong *>(allFreqs.data()));
+    }
+    return ar;
+}
+
+static const JNINativeMethod gFreqTimeMethods[] = {
+        {"removeUidRange", "(II)Z", (void *)KernelCpuUidFreqTimeBpfMapReader_removeUidRange},
+        {"readBpfData", "()Z", (void *)KernelCpuUidFreqTimeBpfMapReader_readBpfData},
+        {"getDataDimensions", "()[J", (void *)KernelCpuUidFreqTimeBpfMapReader_getDataDimensions},
+};
+
+static jboolean KernelCpuUidActiveTimeBpfMapReader_readBpfData(JNIEnv *env, jobject thiz) {
+    static uint64_t lastUpdate = 0;
+    uint64_t newLastUpdate = lastUpdate;
+    auto sparseAr = env->GetObjectField(thiz, gmData);
+    if (sparseAr == NULL) return false;
+    auto data = android::bpf::getUidsUpdatedConcurrentTimes(&newLastUpdate);
+    if (!data.has_value()) return false;
+
+    for (auto &[uid, times] : *data) {
+        // TODO: revise calling code so we can divide by NSEC_PER_MSEC here instead
+        for (auto &time : times.active) time /= NSEC_PER_MSEC;
+        jlongArray ar = getUidArray(env, sparseAr, uid, times.active.size());
+        if (ar == NULL) return false;
+        env->SetLongArrayRegion(ar, 0, times.active.size(),
+                                reinterpret_cast<const jlong *>(times.active.data()));
+    }
+    lastUpdate = newLastUpdate;
+    return true;
+}
+
+static jlongArray KernelCpuUidActiveTimeBpfMapReader_getDataDimensions(JNIEnv *env, jobject) {
+    jlong nCpus = get_nprocs_conf();
+
+    auto ar = env->NewLongArray(1);
+    if (ar != NULL) env->SetLongArrayRegion(ar, 0, 1, &nCpus);
+    return ar;
+}
+
+static const JNINativeMethod gActiveTimeMethods[] = {
+        {"readBpfData", "()Z", (void *)KernelCpuUidActiveTimeBpfMapReader_readBpfData},
+        {"getDataDimensions", "()[J", (void *)KernelCpuUidActiveTimeBpfMapReader_getDataDimensions},
+};
+
+static jboolean KernelCpuUidClusterTimeBpfMapReader_readBpfData(JNIEnv *env, jobject thiz) {
+    static uint64_t lastUpdate = 0;
+    uint64_t newLastUpdate = lastUpdate;
+    auto sparseAr = env->GetObjectField(thiz, gmData);
+    if (sparseAr == NULL) return false;
+    auto data = android::bpf::getUidsUpdatedConcurrentTimes(&newLastUpdate);
+    if (!data.has_value()) return false;
+
+    jsize s = 0;
+    for (auto &[uid, times] : *data) {
+        if (s == 0) {
+            for (const auto &subVec : times.policy) s += subVec.size();
+        }
+        jlongArray ar = getUidArray(env, sparseAr, uid, s);
+        if (ar == NULL) return false;
+        copy2DVecToArray(env, ar, times.policy);
+    }
+    lastUpdate = newLastUpdate;
+    return true;
+}
+
+static jlongArray KernelCpuUidClusterTimeBpfMapReader_getDataDimensions(JNIEnv *env, jobject) {
+    auto times = android::bpf::getUidConcurrentTimes(0);
+    if (!times.has_value()) return NULL;
+
+    std::vector<jlong> clusterCores;
+    for (const auto &vec : times->policy) clusterCores.push_back(vec.size());
+    auto ar = env->NewLongArray(clusterCores.size());
+    if (ar != NULL) env->SetLongArrayRegion(ar, 0, clusterCores.size(), clusterCores.data());
+    return ar;
+}
+
+static const JNINativeMethod gClusterTimeMethods[] = {
+        {"readBpfData", "()Z", (void *)KernelCpuUidClusterTimeBpfMapReader_readBpfData},
+        {"getDataDimensions", "()[J",
+         (void *)KernelCpuUidClusterTimeBpfMapReader_getDataDimensions},
+};
+
+struct readerMethods {
+    const char *name;
+    const JNINativeMethod *methods;
+    int numMethods;
+};
+
+static const readerMethods gAllMethods[] = {
+        {"KernelCpuUidFreqTimeBpfMapReader", gFreqTimeMethods, NELEM(gFreqTimeMethods)},
+        {"KernelCpuUidActiveTimeBpfMapReader", gActiveTimeMethods, NELEM(gActiveTimeMethods)},
+        {"KernelCpuUidClusterTimeBpfMapReader", gClusterTimeMethods, NELEM(gClusterTimeMethods)},
+};
+
+static jboolean KernelCpuUidBpfMapReader_startTrackingBpfTimes(JNIEnv *, jobject) {
+    return android::bpf::startTrackingUidTimes();
+}
+
+int register_com_android_internal_os_KernelCpuUidBpfMapReader(JNIEnv *env) {
+    gSparseArrayClassInfo.clazz = FindClassOrDie(env, "android/util/SparseArray");
+    gSparseArrayClassInfo.clazz = MakeGlobalRefOrDie(env, gSparseArrayClassInfo.clazz);
+    gSparseArrayClassInfo.put =
+            GetMethodIDOrDie(env, gSparseArrayClassInfo.clazz, "put", "(ILjava/lang/Object;)V");
+    gSparseArrayClassInfo.get =
+            GetMethodIDOrDie(env, gSparseArrayClassInfo.clazz, "get", "(I)Ljava/lang/Object;");
+    constexpr auto readerName = "com/android/internal/os/KernelCpuUidBpfMapReader";
+    constexpr JNINativeMethod method = {"startTrackingBpfTimes", "()Z",
+                                        (void *)KernelCpuUidBpfMapReader_startTrackingBpfTimes};
+
+    int ret = RegisterMethodsOrDie(env, readerName, &method, 1);
+    if (ret < 0) return ret;
+    auto c = FindClassOrDie(env, readerName);
+    gmData = GetFieldIDOrDie(env, c, "mData", "Landroid/util/SparseArray;");
+
+    for (const auto &m : gAllMethods) {
+        auto fullName = android::base::StringPrintf("%s$%s", readerName, m.name);
+        ret = RegisterMethodsOrDie(env, fullName.c_str(), m.methods, m.numMethods);
+        if (ret < 0) break;
+    }
+    return ret;
+}
+
+} // namespace android
diff --git a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
new file mode 100644
index 0000000..c0ecf33
--- /dev/null
+++ b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#include "core_jni_helpers.h"
+
+#include <cputimeinstate.h>
+
+namespace android {
+
+static constexpr uint64_t NSEC_PER_MSEC = 1000000;
+
+static jlongArray copyVecsToArray(JNIEnv *env, std::vector<std::vector<uint64_t>> &vec) {
+    jsize s = 0;
+    for (const auto &subVec : vec) s += subVec.size();
+    jlongArray ar = env->NewLongArray(s);
+    jsize start = 0;
+    for (auto &subVec : vec) {
+        for (uint32_t i = 0; i < subVec.size(); ++i) subVec[i] /= NSEC_PER_MSEC;
+        env->SetLongArrayRegion(ar, start, subVec.size(),
+                                reinterpret_cast<const jlong*>(subVec.data()));
+        start += subVec.size();
+    }
+    return ar;
+}
+
+static jlongArray getUidCpuFreqTimeMs(JNIEnv *env, jclass, jint uid) {
+    auto out = android::bpf::getUidCpuFreqTimes(uid);
+    if (!out) return env->NewLongArray(0);
+    return copyVecsToArray(env, out.value());
+}
+
+static const JNINativeMethod g_single_methods[] = {
+    {"readBpfData", "(I)[J", (void *)getUidCpuFreqTimeMs},
+};
+
+int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env) {
+    return RegisterMethodsOrDie(env, "com/android/internal/os/KernelSingleUidTimeReader$Injector",
+                                g_single_methods, NELEM(g_single_methods));
+}
+
+}
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index c039570..6850d01 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -366,6 +366,15 @@
     // Whether battery level is low or not.
     optional bool is_battery_level_low = 8;
 
+    // Denotes which threshold should be used for automatic Battery Saver triggering.
+    enum AutomaticTriggerEnum {
+        TRIGGER_PERCENTAGE = 0;
+        TRIGGER_DYNAMIC = 1;
+    }
+    // The value of Global.AUTOMATIC_POWER_SAVE_MODE. This is a cached value, so it could
+    // be slightly different from what's in GlobalSettingsProto.DynamicPowerSavings.
+    optional AutomaticTriggerEnum setting_automatic_trigger = 19;
+
     // The value of Global.LOW_POWER_MODE. This is a cached value, so it could
     // be slightly different from what's in GlobalSettingsProto.LowPowerMode.
     optional bool setting_battery_saver_enabled = 9;
@@ -390,5 +399,18 @@
     // using elapsed realtime as the timebase.
     optional int64 last_adaptive_battery_saver_changed_externally_elapsed = 17;
 
-    // Next tag: 19
+    // The default disable threshold for Dynamic Power Savings enabled battery saver.
+    optional int32 default_dynamic_disable_threshold = 20;
+
+    // When to disable battery saver again if it was enabled due to an external suggestion.
+    // Corresponds to Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD. This is a cached value,
+    // so it could be slightly different from what's in GlobalSettingsProto.DynamicPowerSavings.
+    optional int32 dynamic_disable_threshold = 21;
+
+    // Whether we've received a suggestion that battery saver should be on from an external app.
+    // Corresponds to Global.DYNAMIC_POWER_SAVINGS_ENABLED. This is a cached value, so it could
+    // be slightly different from what's in GlobalSettingsProto.DynamicPowerSavings.
+    optional bool dynamic_battery_saver_enabled = 22;
+
+    // Next tag: 23
 }
diff --git a/core/res/res/layout/resolve_grid_item.xml b/core/res/res/layout/resolve_grid_item.xml
index 7098c95..9aecfd9 100644
--- a/core/res/res/layout/resolve_grid_item.xml
+++ b/core/res/res/layout/resolve_grid_item.xml
@@ -30,8 +30,8 @@
               android:background="?attr/selectableItemBackgroundBorderless">
 
     <ImageView android:id="@+id/icon"
-               android:layout_width="@dimen/resolver_icon_size"
-               android:layout_height="@dimen/resolver_icon_size"
+               android:layout_width="@dimen/chooser_icon_size"
+               android:layout_height="@dimen/chooser_icon_size"
                android:scaleType="fitCenter" />
 
     <!-- Size manually tuned to match specs -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b82591f..c15b794 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3534,9 +3534,6 @@
         <item>com.android.dialer</item>
     </string-array>
 
-    <!-- An array of packages which can listen for notifications on low ram devices. -->
-    <string-array translatable="false" name="config_allowedManagedServicesOnLowRamDevices" />
-
     <!-- The default value for transition animation scale found in developer settings.
          1.0 corresponds to 1x animator scale, 0 means that there will be no transition
          animations. Note that this is only a default and will be overridden by a
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index c44a0be..48049b4 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -768,6 +768,7 @@
     <dimen name="chooser_header_scroll_elevation">4dp</dimen>
     <dimen name="chooser_max_collapsed_height">288dp</dimen>
     <dimen name="chooser_direct_share_label_placeholder_max_width">72dp</dimen>
+    <dimen name="chooser_icon_size">42dp</dimen>
     <dimen name="resolver_icon_size">32dp</dimen>
     <dimen name="resolver_button_bar_spacing">8dp</dimen>
     <dimen name="resolver_badge_size">18dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 02d90a7..b8dd418 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3167,8 +3167,6 @@
   <java-symbol type="array" name="config_nonBlockableNotificationPackages" />
   <java-symbol type="array" name="config_priorityOnlyDndExemptPackages" />
 
-  <java-symbol type="array" name="config_allowedManagedServicesOnLowRamDevices" />
-
   <!-- Screen-size-dependent modes for picker dialogs. -->
   <java-symbol type="integer" name="time_picker_mode" />
   <java-symbol type="integer" name="date_picker_mode" />
@@ -3765,6 +3763,7 @@
   <java-symbol type="dimen" name="resolver_small_margin"/>
   <java-symbol type="dimen" name="resolver_edge_margin"/>
   <java-symbol type="dimen" name="resolver_elevation"/>
+  <java-symbol type="dimen" name="chooser_icon_size"/>
 
   <!-- For DropBox -->
   <java-symbol type="integer" name="config_dropboxLowPriorityBroadcastRateLimitPeriod" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 18778b9..0785e42 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -114,6 +114,7 @@
         ":FrameworksCoreTests_keyset_splata_api",
         ":FrameworksCoreTests_keyset_splat_api",
         ":FrameworksCoreTests_locales",
+        ":FrameworksCoreTests_overlay_config",
         ":FrameworksCoreTests_version_1",
         ":FrameworksCoreTests_version_1_diff",
         ":FrameworksCoreTests_version_1_nosys",
diff --git a/core/tests/coretests/apks/overlay_config/Android.bp b/core/tests/coretests/apks/overlay_config/Android.bp
new file mode 100644
index 0000000..9573557
--- /dev/null
+++ b/core/tests/coretests/apks/overlay_config/Android.bp
@@ -0,0 +1,4 @@
+android_test_helper_app {
+    name: "FrameworksCoreTests_overlay_config",
+    defaults: ["FrameworksCoreTests_apks_defaults"],
+}
diff --git a/core/tests/coretests/apks/overlay_config/AndroidManifest.xml b/core/tests/coretests/apks/overlay_config/AndroidManifest.xml
new file mode 100644
index 0000000..b15338e
--- /dev/null
+++ b/core/tests/coretests/apks/overlay_config/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.frameworks.coretests.overlay_config">
+
+    <application android:hasCode="false" />
+
+    <uses-sdk android:targetSdkVersion="21"/>
+
+    <overlay android:targetPackage="android"
+             android:targetName="TestResources" />
+</manifest>
diff --git a/core/tests/coretests/src/com/android/internal/content/OverlayConfigIterationRule.java b/core/tests/coretests/src/com/android/internal/content/OverlayConfigIterationRule.java
new file mode 100644
index 0000000..23655a0
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/content/OverlayConfigIterationRule.java
@@ -0,0 +1,162 @@
+/*
+ * 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.internal.content;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.parsing.AndroidPackage;
+import android.os.Build;
+import android.util.ArrayMap;
+
+import com.android.internal.content.om.OverlayConfig.AndroidPackageProvider;
+import com.android.internal.content.om.OverlayScanner;
+import com.android.internal.content.om.OverlayScanner.ParsedOverlayInfo;
+
+import org.junit.Assert;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * A {@link TestRule} that runs a test case twice. First, the test case runs with a non-null
+ * {@link OverlayScanner} as if the zygote process is scanning the overlay packages
+ * and parsing configuration files. The test case then runs with a non-null
+ * {@link AndroidPackageProvider} as if the system server is parsing configuration files.
+ *
+ * This simulates what will happen on device. If an exception would be thrown in the zygote, then
+ * the exception should be thrown in the first run of the test case.
+ */
+public class OverlayConfigIterationRule implements TestRule {
+
+    enum Iteration {
+        ZYGOTE,
+        SYSTEM_SERVER,
+    }
+
+    private final ArrayMap<File, ParsedOverlayInfo> mOverlayStubResults = new ArrayMap<>();
+    private Supplier<OverlayScanner> mOverlayScanner;
+    private AndroidPackageProvider mAndroidPackageProvider;
+    private Iteration mIteration;
+
+    /**
+     * Mocks the parsing of the file to make it appear to the scanner that the file is a valid
+     * overlay APK.
+     **/
+    void addOverlay(File path, String packageName, String targetPackage, int targetSdkVersion,
+            boolean isStatic, int priority) {
+        try {
+            final File canonicalPath = new File(path.getCanonicalPath());
+            mOverlayStubResults.put(canonicalPath, new ParsedOverlayInfo(
+                    packageName, targetPackage, targetSdkVersion, isStatic, priority,
+                    canonicalPath));
+        } catch (IOException e) {
+            Assert.fail("Failed to add overlay " + e);
+        }
+    }
+
+    void addOverlay(File path, String packageName) {
+        addOverlay(path, packageName, "target");
+    }
+
+    void addOverlay(File path, String packageName, String targetPackage) {
+        addOverlay(path, packageName, targetPackage, Build.VERSION_CODES.CUR_DEVELOPMENT);
+    }
+
+    void addOverlay(File path, String packageName, String targetPackage, int targetSdkVersion) {
+        addOverlay(path, packageName, targetPackage, targetSdkVersion, false, 0);
+    }
+
+    /** Retrieves the {@link OverlayScanner} for the current run of the test. */
+    Supplier<OverlayScanner> getScannerFactory() {
+        return mOverlayScanner;
+    }
+
+    /** Retrieves the {@link AndroidPackageProvider} for the current run of the test. */
+    AndroidPackageProvider getPackageProvider() {
+        return mAndroidPackageProvider;
+    }
+
+    /** Retrieves the current iteration of the test. */
+    Iteration getIteration() {
+        return mIteration;
+    }
+
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                // Run the test once as if the zygote process is scanning the overlay packages
+                // and parsing configuration files.
+                mOverlayScanner = () -> {
+                    OverlayScanner scanner = Mockito.spy(new OverlayScanner());
+                    for (Map.Entry<File, ParsedOverlayInfo> overlay :
+                            mOverlayStubResults.entrySet()) {
+                        doReturn(overlay.getValue()).when(scanner)
+                                .parseOverlayManifest(overlay.getKey());
+                    }
+                    return scanner;
+                };
+                mAndroidPackageProvider = null;
+                mIteration = Iteration.ZYGOTE;
+                base.evaluate();
+
+                // Run the test once more (if the first test did not throw an exception) as if
+                // the system server is parsing the configuration files and using PackageManager to
+                // retrieving information of overlays.
+                mOverlayScanner = null;
+                mAndroidPackageProvider = Mockito.mock(AndroidPackageProvider.class);
+                mIteration = Iteration.SYSTEM_SERVER;
+                doAnswer((InvocationOnMock invocation) -> {
+                    final Object[] args = invocation.getArguments();
+                    final Consumer<AndroidPackage> f =  (Consumer<AndroidPackage>) args[0];
+                    for (Map.Entry<File, ParsedOverlayInfo> overlay :
+                            mOverlayStubResults.entrySet()) {
+                        final AndroidPackage a = Mockito.mock(AndroidPackage.class);
+                        final ParsedOverlayInfo info = overlay.getValue();
+                        when(a.getPackageName()).thenReturn(info.packageName);
+                        when(a.getOverlayTarget()).thenReturn(info.targetPackageName);
+                        when(a.getTargetSdkVersion()).thenReturn(info.targetSdkVersion);
+                        when(a.isOverlayIsStatic()).thenReturn(info.isStatic);
+                        when(a.getOverlayPriority()).thenReturn(info.priority);
+                        when(a.getBaseCodePath()).thenReturn(info.path.getPath());
+                        when(a.isSystem()).thenReturn(
+                                !info.path.getPath().contains("data/overlay"));
+                        f.accept(a);
+                    }
+                    return null;
+                }).when(mAndroidPackageProvider).forEachPackage(any());
+
+                base.evaluate();
+            }
+        };
+    }
+}
+
+
diff --git a/core/tests/coretests/src/com/android/internal/content/OverlayConfigTest.java b/core/tests/coretests/src/com/android/internal/content/OverlayConfigTest.java
new file mode 100644
index 0000000..dee118f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/content/OverlayConfigTest.java
@@ -0,0 +1,603 @@
+/*
+ * 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.internal.content;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.os.FileUtils;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.content.om.OverlayConfig.IdmapInvocation;
+import com.android.internal.content.om.OverlayScanner;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public class OverlayConfigTest {
+    private static final String TEST_APK_PACKAGE_NAME =
+            "com.android.frameworks.coretests.overlay_config";
+
+    private ExpectedException mExpectedException = ExpectedException.none();
+    private OverlayConfigIterationRule mScannerRule = new OverlayConfigIterationRule();
+    private TemporaryFolder mTestFolder = new TemporaryFolder();
+
+    @Rule
+    public RuleChain chain = RuleChain.outerRule(mExpectedException)
+            .around(mTestFolder).around(mScannerRule);
+
+    private OverlayConfig createConfigImpl() throws IOException {
+        return new OverlayConfig(mTestFolder.getRoot().getCanonicalFile(),
+                mScannerRule.getScannerFactory(), mScannerRule.getPackageProvider());
+    }
+
+    private File createFile(String fileName) throws IOException {
+        return createFile(fileName, "");
+    }
+
+    private File createFile(String fileName, String content) throws IOException {
+        final File f = new File(String.format("%s/%s", mTestFolder.getRoot(), fileName));
+        if (!f.getParentFile().equals(mTestFolder.getRoot())) {
+            f.getParentFile().mkdirs();
+        }
+        FileUtils.stringToFile(f.getPath(), content);
+        return f;
+    }
+
+    private static void assertConfig(OverlayConfig overlayConfig, String packageName,
+            boolean mutable, boolean enabled, int configIndex) {
+        final OverlayConfig.Configuration config = overlayConfig.getConfiguration(packageName);
+        assertNotNull(config);
+        assertEquals(mutable, config.parsedConfig.mutable);
+        assertEquals(enabled, config.parsedConfig.enabled);
+        assertEquals(configIndex, config.configIndex);
+    }
+
+    @Test
+    public void testImmutableAfterNonImmutableFails() throws IOException {
+        mExpectedException.expect(IllegalStateException.class);
+        mExpectedException.expectMessage("immutable overlays must precede mutable overlays");
+
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" enabled=\"true\" />"
+                        + "  <overlay package=\"two\" mutable=\"false\" enabled=\"true\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one");
+        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two");
+        createConfigImpl();
+    }
+
+    @Test
+    public void testConfigureAbsentPackageFails() throws IOException {
+        mExpectedException.expect(IllegalStateException.class);
+        mExpectedException.expectMessage("not present in partition");
+
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" enabled=\"true\" />"
+                        + "</config>");
+
+        createConfigImpl();
+    }
+
+    @Test
+    public void testConfigurePackageTwiceFails() throws IOException {
+        mExpectedException.expect(IllegalStateException.class);
+        mExpectedException.expectMessage("configured multiple times in a single partition");
+
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" enabled=\"true\" />"
+                        + "  <overlay package=\"one\" mutable=\"false\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one");
+        createConfigImpl();
+    }
+
+    @Test
+    public void testConfigureOverlayAcrossPartitionsFails() throws IOException {
+        mExpectedException.expect(IllegalStateException.class);
+        mExpectedException.expectMessage("not present in partition");
+
+        createFile("/vendor/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" enabled=\"true\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one");
+        createConfigImpl();
+    }
+
+    @Test
+    public void testConfigureOverlayOutsideOverlayDirFails() throws IOException {
+        mExpectedException.expect(IllegalStateException.class);
+        mExpectedException.expectMessage("not present in partition");
+
+        createFile("/vendor/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" enabled=\"true\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/product/app/one.apk"), "one");
+        createConfigImpl();
+    }
+
+    @Test
+    public void testMergeOAbsolutePathFails() throws IOException {
+        mExpectedException.expect(IllegalStateException.class);
+        mExpectedException.expectMessage("must be relative to the directory");
+
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <merge path=\"/product/overlay/config/auto-generated-config.xml\" />"
+                        + "</config>");
+
+        createConfigImpl();
+    }
+
+    @Test
+    public void testMergeOutsideDirFails() throws IOException {
+        mExpectedException.expect(IllegalStateException.class);
+        mExpectedException.expectMessage("outside of configuration directory");
+
+        createFile("/product/overlay/auto-generated-config.xml");
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <merge path=\"../auto-generated-config.xml\" />"
+                        + "</config>");
+
+        createConfigImpl();
+    }
+
+    @Test
+    public void testMergeOutsidePartitionFails() throws IOException {
+        mExpectedException.expect(IllegalStateException.class);
+        mExpectedException.expectMessage("outside of configuration directory");
+
+        createFile("/vendor/overlay/config/config2.xml");
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <merge path=\"../../../vendor/overlay/config/config2.xml\" />"
+                        + "</config>");
+
+        createConfigImpl();
+    }
+
+    @Test
+    public void testMergeCircularFails() throws IOException {
+        mExpectedException.expect(IllegalStateException.class);
+        mExpectedException.expectMessage("Maximum <merge> depth exceeded");
+
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <merge path=\"config2.xml\" />"
+                        + "</config>");
+        createFile("/product/overlay/config/config2.xml",
+                "<config>"
+                        + "  <merge path=\"config.xml\" />"
+                        + "</config>");
+
+        createConfigImpl();
+    }
+
+    @Test
+    public void testMergeMissingFileFails() throws IOException {
+        mExpectedException.expect(IllegalStateException.class);
+        mExpectedException.expectMessage("does not exist");
+
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <merge path=\"config2.xml\" />"
+                        + "</config>");
+        createConfigImpl();
+    }
+
+    @Test
+    public void testProductOverridesVendor() throws IOException {
+        createFile("/vendor/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" enabled=\"false\" />"
+                        + "</config>");
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" enabled=\"true\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one");
+        mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one");
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertConfig(overlayConfig, "one", true, true, 1);
+    }
+
+    @Test
+    public void testPartitionPrecedence() throws IOException {
+        createFile("/vendor/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" enabled=\"true\" />"
+                        + "</config>");
+        createFile("/odm/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"two\" enabled=\"true\" />"
+                        + "</config>");
+        createFile("/oem/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"three\" enabled=\"true\" />"
+                        + "</config>");
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"four\" enabled=\"true\" />"
+                        + "</config>");
+        createFile("/system_ext/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"five\" enabled=\"true\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one");
+        mScannerRule.addOverlay(createFile("/odm/overlay/two.apk"), "two");
+        mScannerRule.addOverlay(createFile("/oem/overlay/three.apk"), "three");
+        mScannerRule.addOverlay(createFile("/product/overlay/four.apk"), "four");
+        mScannerRule.addOverlay(createFile("/system_ext/overlay/five.apk"), "five");
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertConfig(overlayConfig, "one", true, true, 0);
+        assertConfig(overlayConfig, "two", true, true, 1);
+        assertConfig(overlayConfig, "three", true, true, 2);
+        assertConfig(overlayConfig, "four", true, true, 3);
+        assertConfig(overlayConfig, "five", true, true, 4);
+    }
+
+    @Test
+    public void testImmutable() throws IOException {
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" mutable=\"false\" />"
+                        + "  <overlay package=\"two\" />"
+                        + "  <overlay package=\"three\" mutable=\"true\" />"
+                        + "</config>");
+
+
+        mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one");
+        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two");
+        mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three");
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertConfig(overlayConfig, "one", false, false, 0);
+        assertConfig(overlayConfig, "two", true, false, 1);
+        assertConfig(overlayConfig, "three", true, false, 2);
+    }
+
+    @Test
+    public void testEnabled() throws IOException {
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" />"
+                        + "  <overlay package=\"two\" enabled=\"true\" />"
+                        + "  <overlay package=\"three\" enabled=\"false\" />"
+                        + "</config>");
+
+
+        mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one");
+        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two");
+        mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three");
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertConfig(overlayConfig, "one", true, false, 0);
+        assertConfig(overlayConfig, "two", true, true, 1);
+        assertConfig(overlayConfig, "three", true, false, 2);
+    }
+
+    @Test
+    public void testMerge() throws IOException {
+        createFile("/product/overlay/config/auto-generated-config.xml",
+                "<config>"
+                        + "  <overlay package=\"two\" mutable=\"false\" enabled=\"true\" />"
+                        + "  <overlay package=\"three\" mutable=\"false\" enabled=\"true\" />"
+                        + "</config>");
+
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" mutable=\"false\" enabled=\"true\" />"
+                        + "  <merge path=\"auto-generated-config.xml\" />"
+                        + "  <overlay package=\"four\" enabled=\"true\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one");
+        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two");
+        mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three");
+        mScannerRule.addOverlay(createFile("/product/overlay/four.apk"), "four");
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        OverlayConfig.Configuration o1 = overlayConfig.getConfiguration("one");
+        assertNotNull(o1);
+        assertFalse(o1.parsedConfig.mutable);
+        assertTrue(o1.parsedConfig.enabled);
+        assertEquals(0, o1.configIndex);
+
+        OverlayConfig.Configuration o2 = overlayConfig.getConfiguration("two");
+        assertNotNull(o2);
+        assertFalse(o2.parsedConfig.mutable);
+        assertTrue(o2.parsedConfig.enabled);
+        assertEquals(1, o2.configIndex);
+
+        OverlayConfig.Configuration o3 = overlayConfig.getConfiguration("three");
+        assertNotNull(o3);
+        assertFalse(o3.parsedConfig.mutable);
+        assertTrue(o3.parsedConfig.enabled);
+        assertEquals(2, o3.configIndex);
+
+        OverlayConfig.Configuration o4 = overlayConfig.getConfiguration("four");
+        assertNotNull(o4);
+        assertTrue(o4.parsedConfig.mutable);
+        assertTrue(o4.parsedConfig.enabled);
+        assertEquals(3, o4.configIndex);
+    }
+
+    @Test
+    public void testIdmapInvocationsFrameworkImmutable() throws IOException {
+        createFile("/vendor/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" mutable=\"false\" enabled=\"true\" />"
+                        + "  <overlay package=\"two\" mutable=\"false\" enabled=\"true\" />"
+                        + "  <overlay package=\"three\" enabled=\"true\" />"
+                        + "</config>");
+
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"four\" mutable=\"false\" enabled=\"true\" />"
+                        + "  <overlay package=\"five\" mutable=\"false\" enabled=\"true\" />"
+                        + "  <overlay package=\"six\" mutable=\"false\" enabled=\"false\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android");
+        mScannerRule.addOverlay(createFile("/vendor/overlay/two.apk"), "two", "android");
+        mScannerRule.addOverlay(createFile("/vendor/overlay/three.apk"), "three", "android");
+        mScannerRule.addOverlay(createFile("/product/overlay/four.apk"), "four", "android");
+        mScannerRule.addOverlay(createFile("/product/overlay/five.apk"), "five");
+        mScannerRule.addOverlay(createFile("/product/overlay/six.apk"), "six", "android");
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        if (mScannerRule.getIteration() == OverlayConfigIterationRule.Iteration.ZYGOTE) {
+            final ArrayList<IdmapInvocation> idmapInvocations =
+                    overlayConfig.getImmutableFrameworkOverlayIdmapInvocations();
+            assertEquals(2, idmapInvocations.size());
+
+            final IdmapInvocation i0 = idmapInvocations.get(0);
+            assertTrue(i0.enforceOverlayable);
+            assertEquals("vendor", i0.policy);
+            assertEquals(2, i0.overlayPaths.size());
+            assertTrue(i0.overlayPaths.get(0).endsWith("/vendor/overlay/one.apk"));
+            assertTrue(i0.overlayPaths.get(1).endsWith("/vendor/overlay/two.apk"));
+
+            final IdmapInvocation i1 = idmapInvocations.get(1);
+            assertTrue(i1.enforceOverlayable);
+            assertEquals("product", i1.policy);
+            assertEquals(1, i1.overlayPaths.size());
+            assertTrue(i1.overlayPaths.get(0).endsWith("/product/overlay/four.apk"));
+        }
+    }
+
+    @Test
+    public void testIdmapInvocationsDifferentTargetSdk() throws IOException {
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" mutable=\"false\" enabled=\"true\" />"
+                        + "  <overlay package=\"two\" mutable=\"false\" enabled=\"true\" />"
+                        + "  <overlay package=\"three\" mutable=\"false\" enabled=\"true\" />"
+                        + "  <overlay package=\"four\" mutable=\"false\" enabled=\"true\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one", "android");
+        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android");
+        mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three", "android", 28);
+        mScannerRule.addOverlay(createFile("/product/overlay/four.apk"), "four", "android");
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+
+        if (mScannerRule.getIteration() == OverlayConfigIterationRule.Iteration.ZYGOTE) {
+            final ArrayList<IdmapInvocation> idmapInvocations =
+                    overlayConfig.getImmutableFrameworkOverlayIdmapInvocations();
+            assertEquals(3, idmapInvocations.size());
+
+            final IdmapInvocation i0 = idmapInvocations.get(0);
+            assertTrue(i0.enforceOverlayable);
+            assertEquals(2, i0.overlayPaths.size());
+            assertTrue(i0.overlayPaths.get(0).endsWith("/product/overlay/one.apk"));
+            assertTrue(i0.overlayPaths.get(1).endsWith("/product/overlay/two.apk"));
+
+            final IdmapInvocation i1 = idmapInvocations.get(1);
+            assertFalse(i1.enforceOverlayable);
+            assertEquals(1, i1.overlayPaths.size());
+            assertTrue(i1.overlayPaths.get(0).endsWith("/product/overlay/three.apk"));
+
+            final IdmapInvocation i2 = idmapInvocations.get(2);
+            assertTrue(i2.enforceOverlayable);
+            assertEquals(1, i2.overlayPaths.size());
+            assertTrue(i2.overlayPaths.get(0).endsWith("/product/overlay/four.apk"));
+        }
+    }
+
+    @Test
+    public void testNoConfigIsStatic() throws IOException {
+        mScannerRule.addOverlay(createFile("/product/overlay/one.apk"), "one", "android", 28, true,
+                1);
+        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 28, false,
+                0);
+        mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three", "android", 28,
+                true, 0);
+        mScannerRule.addOverlay(createFile("/product/overlay/four.apk"), "four", "android", 28,
+                false, 2);
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertConfig(overlayConfig, "one", false, true, 1);
+        assertConfig(overlayConfig, "three", false, true, 0);
+
+    }
+
+    @Test
+    public void testVendorStaticPrecedesProductImmutable() throws IOException {
+        createFile("/product/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"two\" mutable=\"false\" enabled=\"true\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
+                1);
+        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true,
+                0);
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertConfig(overlayConfig, "one", false, true, 0);
+        assertConfig(overlayConfig, "two", false, true, 1);
+    }
+
+    @Test
+    public void testVendorImmutablePrecededProductStatic() throws IOException {
+        createFile("/vendor/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"one\" mutable=\"false\" enabled=\"true\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
+                1);
+        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true,
+                0);
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertConfig(overlayConfig, "one", false, true, 0);
+        assertConfig(overlayConfig, "two", false, true, 1);
+    }
+
+    @Test
+    public void testNoConfigsAllowPartitionReordering() throws IOException {
+        mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
+                1);
+        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true,
+                0);
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertConfig(overlayConfig, "one", false, true, 1);
+        assertConfig(overlayConfig, "two", false, true, 0);
+    }
+
+    @Test
+    public void testConfigDisablesPartitionReordering() throws IOException {
+        createFile("/odm/overlay/config/config.xml",
+                "<config>"
+                        + "  <overlay package=\"two\" enabled=\"true\" />"
+                        + "</config>");
+
+        mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
+                1);
+        mScannerRule.addOverlay(createFile("/odm/overlay/two.apk"), "two");
+        mScannerRule.addOverlay(createFile("/product/overlay/three.apk"), "three", "android", 0,
+                true, 0);
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertConfig(overlayConfig, "one", false, true, 0);
+        assertConfig(overlayConfig, "two", true, true, 1);
+        assertConfig(overlayConfig, "three", false, true, 2);
+    }
+
+    @Test
+    public void testStaticOverlayOutsideOverlayDir() throws IOException {
+        mScannerRule.addOverlay(createFile("/product/app/one.apk"), "one", "android", 0, true, 0);
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        if (mScannerRule.getIteration() == OverlayConfigIterationRule.Iteration.SYSTEM_SERVER) {
+            assertConfig(overlayConfig, "one", false, true, 0);
+        }
+    }
+
+    @Test
+    public void testSortStaticOverlaysDifferentTargets() throws IOException {
+        mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "other", 0, true, 0);
+        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true,
+                0);
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertConfig(overlayConfig, "one", false, true, 1);
+        assertConfig(overlayConfig, "two", false, true, 0);
+    }
+
+    @Test
+    public void testSortStaticOverlaysSamePriority() throws IOException {
+        mScannerRule.addOverlay(createFile("/vendor/overlay/one.apk"), "one", "android", 0, true,
+                0);
+        mScannerRule.addOverlay(createFile("/product/overlay/two.apk"), "two", "android", 0, true,
+                0);
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertConfig(overlayConfig, "one", false, true, 1);
+        assertConfig(overlayConfig, "two", false, true, 0);
+    }
+
+    @Test
+    public void testNonSystemOverlayCannotBeStatic() throws IOException {
+        mScannerRule.addOverlay(createFile("/data/overlay/one.apk"), "one", "android", 0, true,
+                0);
+
+        final OverlayConfig overlayConfig = createConfigImpl();
+        assertTrue(overlayConfig.isMutable("one"));
+        assertFalse(overlayConfig.isEnabled("one"));
+        assertEquals(Integer.MAX_VALUE, overlayConfig.getPriority("one"));
+    }
+
+    @Test
+    public void testGetOverlayInfo() throws IOException {
+        if (mScannerRule.getIteration() != OverlayConfigIterationRule.Iteration.ZYGOTE) {
+            // Run only one iteration of the test.
+            return;
+        }
+
+        final InputStream is = InstrumentationRegistry.getContext().getResources()
+                .openRawResource(R.raw.overlay_config);
+        final File partitionDir = mTestFolder.newFolder("product", "overlay");
+        final File testApk = new File(partitionDir, "test.apk");
+        FileUtils.copy(is, new FileOutputStream(testApk));
+
+        final OverlayScanner scanner = new OverlayScanner();
+        scanner.scanDir(partitionDir);
+
+        final OverlayScanner.ParsedOverlayInfo info = scanner.getParsedInfo(TEST_APK_PACKAGE_NAME);
+        assertNotNull(info);
+        assertEquals(TEST_APK_PACKAGE_NAME, info.packageName);
+        assertEquals("android", info.targetPackageName);
+        assertEquals(testApk.getPath(), info.path.getPath());
+        assertEquals(21, info.targetSdkVersion);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index a6329298..2ad8e18 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -40,6 +40,7 @@
         BatteryStatsUserLifecycleTests.class,
         KernelCpuProcStringReaderTest.class,
         KernelCpuUidActiveTimeReaderTest.class,
+        KernelCpuUidBpfMapReaderTest.class,
         KernelCpuUidClusterTimeReaderTest.class,
         KernelCpuUidFreqTimeReaderTest.class,
         KernelCpuUidUserSysTimeReaderTest.class,
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
index 1b13a99..2ccd74e 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidActiveTimeReaderTest.java
@@ -21,11 +21,11 @@
 
 import android.content.Context;
 import android.os.FileUtils;
+import android.util.SparseArray;
 import android.util.SparseLongArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
 
@@ -33,11 +33,15 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Random;
 
 /**
@@ -46,14 +50,16 @@
  * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidActiveTimeReaderTest
  */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class KernelCpuUidActiveTimeReaderTest {
     private File mTestDir;
     private File mTestFile;
     private KernelCpuUidActiveTimeReader mReader;
+    private KernelCpuUidTestBpfMapReader mBpfMapReader;
     private VerifiableCallback mCallback;
 
     private Random mRand = new Random(12345);
+    protected boolean mUseBpf;
     private final int mCpus = 4;
     private final String mHeadline = "cpus: 4\n";
     private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
@@ -62,12 +68,22 @@
         return InstrumentationRegistry.getContext();
     }
 
+    @Parameters(name="useBpf={0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] { {true}, {false} });
+    }
+
+    public KernelCpuUidActiveTimeReaderTest(boolean useBpf) {
+        mUseBpf = useBpf;
+    }
+
     @Before
     public void setUp() {
         mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
         mTestFile = new File(mTestDir, "test.file");
+        mBpfMapReader = new KernelCpuUidTestBpfMapReader();
         mReader = new KernelCpuUidActiveTimeReader(
-                new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+                new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
         mCallback = new VerifiableCallback();
     }
 
@@ -80,7 +96,7 @@
     @Test
     public void testReadDelta() throws Exception {
         final long[][] times = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times));
+        setCpusAndData(times);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], getActiveTime(times[i]));
@@ -90,7 +106,7 @@
         // Verify that a second call will only return deltas.
         mCallback.clear();
         final long[][] newTimes1 = increaseTime(times);
-        writeToFile(mHeadline + uidLines(mUids, newTimes1));
+        setCpusAndData(newTimes1);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], getActiveTime(newTimes1[i]) - getActiveTime(times[i]));
@@ -105,7 +121,7 @@
         // Verify that calling with a null callback doesn't result in any crashes
         mCallback.clear();
         final long[][] newTimes2 = increaseTime(newTimes1);
-        writeToFile(mHeadline + uidLines(mUids, newTimes2));
+        setCpusAndData(newTimes2);
         mReader.readDelta(null);
         mCallback.verifyNoMoreInteractions();
 
@@ -113,19 +129,20 @@
         // the previous call had null callback.
         mCallback.clear();
         final long[][] newTimes3 = increaseTime(newTimes2);
+        setCpusAndData(newTimes3);
         writeToFile(mHeadline + uidLines(mUids, newTimes3));
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], getActiveTime(newTimes3[i]) - getActiveTime(newTimes2[i]));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCpusAndData();
     }
 
     @Test
     public void testReadAbsolute() throws Exception {
         final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times1));
+        setCpusAndData(times1);
         mReader.readAbsolute(mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], getActiveTime(times1[i]));
@@ -135,19 +152,19 @@
         // Verify that a second call should still return absolute values
         mCallback.clear();
         final long[][] times2 = increaseTime(times1);
-        writeToFile(mHeadline + uidLines(mUids, times2));
+        setCpusAndData(times2);
         mReader.readAbsolute(mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], getActiveTime(times2[i]));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCpusAndData();
     }
 
     @Test
     public void testReadDeltaDecreasedTime() throws Exception {
         final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times1));
+        setCpusAndData(times1);
         mReader.readDelta(mCallback);
 
         // Verify that there should not be a callback for a particular UID if its time decreases.
@@ -155,19 +172,19 @@
         final long[][] times2 = increaseTime(times1);
         System.arraycopy(times1[0], 0, times2[0], 0, mCpus);
         times2[0][0] = 100;
-        writeToFile(mHeadline + uidLines(mUids, times2));
+        setCpusAndData(times2);
         mReader.readDelta(mCallback);
         for (int i = 1; i < mUids.length; i++) {
             mCallback.verify(mUids[i], getActiveTime(times2[i]) - getActiveTime(times1[i]));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCpusAndData();
 
         // Verify that the internal state was not modified.
         mCallback.clear();
         final long[][] times3 = increaseTime(times2);
         times3[0] = increaseTime(times1)[0];
-        writeToFile(mHeadline + uidLines(mUids, times3));
+        setCpusAndData(times3);
         mReader.readDelta(mCallback);
         mCallback.verify(mUids[0], getActiveTime(times3[0]) - getActiveTime(times1[0]));
         for (int i = 1; i < mUids.length; i++) {
@@ -179,26 +196,26 @@
     @Test
     public void testReadDeltaNegativeTime() throws Exception {
         final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times1));
+        setCpusAndData(times1);
         mReader.readDelta(mCallback);
 
         // Verify that there should not be a callback for a particular UID if its time is -ve.
         mCallback.clear();
         final long[][] times2 = increaseTime(times1);
         times2[0][0] *= -1;
-        writeToFile(mHeadline + uidLines(mUids, times2));
+        setCpusAndData(times2);
         mReader.readDelta(mCallback);
         for (int i = 1; i < mUids.length; i++) {
             mCallback.verify(mUids[i], getActiveTime(times2[i]) - getActiveTime(times1[i]));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCpusAndData();
 
         // Verify that the internal state was not modified.
         mCallback.clear();
         final long[][] times3 = increaseTime(times2);
         times3[0] = increaseTime(times1)[0];
-        writeToFile(mHeadline + uidLines(mUids, times3));
+        setCpusAndData(times3);
         mReader.readDelta(mCallback);
         mCallback.verify(mUids[0], getActiveTime(times3[0]) - getActiveTime(times1[0]));
         for (int i = 1; i < mUids.length; i++) {
@@ -207,6 +224,28 @@
         mCallback.verifyNoMoreInteractions();
     }
 
+    private void setCpusAndData(long[][] times) throws IOException {
+        if (mUseBpf) {
+            mBpfMapReader.setCpus(new long[]{ mCpus });
+            SparseArray<long[]> data = new SparseArray<>();
+            for (int i = 0; i < mUids.length; i++) {
+                data.put(mUids[i], times[i]);
+            }
+            mBpfMapReader.setData(data);
+        } else {
+            writeToFile(mHeadline + uidLines(mUids, times));
+        }
+    }
+
+    private void clearCpusAndData() {
+        if (mUseBpf) {
+            mBpfMapReader.setCpus(null);
+            mBpfMapReader.setData(new SparseArray<>());
+        } else {
+            assertTrue(mTestFile.delete());
+        }
+    }
+
     private String uidLines(int[] uids, long[][] times) {
         StringBuffer sb = new StringBuffer();
         for (int i = 0; i < uids.length; i++) {
@@ -261,4 +300,36 @@
             assertEquals(0, mData.size());
         }
     }
+
+    private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+        private long[] mCpus;
+        private SparseArray<long[]> mNewData = new SparseArray<>();
+
+        public void setData(SparseArray<long[]> data) {
+            mNewData = data;
+        }
+
+        public void setCpus(long[] cpus) {
+            mCpus = cpus;
+        }
+
+        @Override
+        public final boolean startTrackingBpfTimes() {
+            return true;
+        }
+
+        @Override
+        protected final boolean readBpfData() {
+            if (!mUseBpf) {
+                return false;
+            }
+            mData = mNewData;
+            return true;
+        }
+
+        @Override
+        public final long[] getDataDimensions() {
+            return mCpus;
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
new file mode 100644
index 0000000..257b388
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidBpfMapReaderTest.java
@@ -0,0 +1,237 @@
+/*
+ * 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.internal.os;
+
+import static org.junit.Assert.assertArrayEquals;
+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 android.content.Context;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+
+import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelCpuUidBpfMapReaderTest {
+    private Random mRand = new Random(12345);
+    private KernelCpuUidTestBpfMapReader mReader;
+
+    private Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+
+    @Before
+    public void setUp() {
+        mReader =  new KernelCpuUidTestBpfMapReader();
+    }
+
+    /**
+     * Tests that reading returns null if readBpfData() fails.
+     */
+    @Test
+    public void testUnsuccessfulRead() {
+        assertEquals(null, mReader.open());
+    }
+
+    /**
+     * Tests that reading will always return null after 5 failures.
+     */
+    @Test
+    public void testReadErrorsLimit() {
+        for (int i = 0; i < 3; i++) {
+            try (BpfMapIterator iter = mReader.open()) {
+                assertNull(iter);
+            }
+        }
+
+        SparseArray<long[]> data = new SparseArray<>();
+        long[] times = {2};
+        data.put(1, new long[]{2});
+        mReader.setData(data);
+        testOpenAndReadData(data);
+
+        mReader.setData(null);
+        for (int i = 0; i < 3; i++) {
+            try (BpfMapIterator iter = mReader.open(true)) {
+                assertNull(iter);
+            }
+        }
+        mReader.setData(data);
+        try (BpfMapIterator iter = mReader.open(true)) {
+            assertNull(iter);
+        }
+    }
+
+    /** Tests getNextUid functionality. */
+    @Test
+    public void testGetNextUid() {
+        final SparseArray<long[]> data = getTestSparseArray(800, 50);
+        mReader.setData(data);
+        testOpenAndReadData(data);
+    }
+
+    @Test
+    public void testConcurrent() throws Exception {
+        final SparseArray<long[]> data = getTestSparseArray(200, 50);
+        final SparseArray<long[]> data1 = getTestSparseArray(180, 70);
+        final List<Throwable> errs = Collections.synchronizedList(new ArrayList<>());
+        mReader.setData(data);
+        // An additional thread for modifying the data.
+        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(11);
+        final CountDownLatch ready = new CountDownLatch(10);
+        final CountDownLatch start = new CountDownLatch(1);
+        final CountDownLatch modify = new CountDownLatch(1);
+        final CountDownLatch done = new CountDownLatch(10);
+
+        for (int i = 0; i < 5; i++) {
+            threadPool.submit(() -> {
+                    ready.countDown();
+                    try {
+                        start.await();
+                        testOpenAndReadData(data);
+                    } catch (Throwable e) {
+                        errs.add(e);
+                    } finally {
+                        done.countDown();
+                    }
+            });
+            threadPool.submit(() -> {
+                    ready.countDown();
+                    try {
+                        start.await();
+                        // Wait for data modification.
+                        modify.await();
+                        testOpenAndReadData(data1);
+                    } catch (Throwable e) {
+                        errs.add(e);
+                    } finally {
+                        done.countDown();
+                    }
+             });
+        }
+
+        assertTrue("Prep timed out", ready.await(100, TimeUnit.MILLISECONDS));
+        start.countDown();
+
+        threadPool.schedule(() -> {
+                mReader.setData(data1);
+                modify.countDown();
+        }, 600, TimeUnit.MILLISECONDS);
+
+        assertTrue("Execution timed out", done.await(3, TimeUnit.SECONDS));
+        threadPool.shutdownNow();
+
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        errs.forEach(e -> e.printStackTrace(pw));
+
+        assertTrue("All Exceptions:\n" + sw.toString(), errs.isEmpty());
+    }
+
+    @Test
+    public void testRemoveUidsInRange() {
+        final SparseArray<long[]> data = getTestSparseArray(200, 50);
+        mReader.setData(data);
+        testOpenAndReadData(data);
+        SparseArray<long[]> changedData = new SparseArray<>();
+        for (int i = 6; i < 200; i++) {
+            changedData.put(i, data.get(i));
+        }
+        mReader.removeUidsInRange(0, 5);
+        testOpenAndReadData(changedData);
+    }
+
+    private void testOpenAndReadData(SparseArray<long[]> expectedData) {
+        try (BpfMapIterator iter = mReader.open()) {
+            long[] actual;
+            if (expectedData.size() > 0) {
+                actual = new long[expectedData.valueAt(0).length + 1];
+            } else {
+                actual = new long[0];
+            }
+            for (int i = 0; i < expectedData.size(); i++) {
+                assertTrue(iter.getNextUid(actual));
+                assertEquals(expectedData.keyAt(i), actual[0]);
+                assertArrayEquals(expectedData.valueAt(i), Arrays.copyOfRange(actual, 1, actual.length));
+            }
+            assertFalse(iter.getNextUid(actual));
+            assertFalse(iter.getNextUid(actual));
+        }
+    }
+
+
+    private SparseArray<long[]> getTestSparseArray(int uids, int numPerUid) {
+        SparseArray<long[]> data = new SparseArray<>();
+        for (int i = 0; i < uids; i++) {
+            data.put(i, mRand.longs(numPerUid, 0, Long.MAX_VALUE).toArray());
+        }
+        return data;
+    }
+
+
+    private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+        private SparseArray<long[]> mNewData;
+
+        public final void setData(SparseArray<long[]> newData) {
+            mNewData = newData;
+        }
+
+        @Override
+        public final boolean startTrackingBpfTimes() {
+            return true;
+        }
+
+        @Override
+        public final boolean readBpfData() {
+            if (mNewData == null) {
+                return false;
+            }
+            mData = mNewData;
+            return true;
+        }
+
+        @Override
+        public final long[] getDataDimensions() {
+            return null;
+        }
+
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
index 2ea80da..a0dab28 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidClusterTimeReaderTest.java
@@ -27,7 +27,6 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
 
@@ -35,11 +34,15 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Random;
 
 /**
@@ -48,28 +51,42 @@
  * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidClusterTimeReaderTest
  */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class KernelCpuUidClusterTimeReaderTest {
     private File mTestDir;
     private File mTestFile;
     private KernelCpuUidClusterTimeReader mReader;
+    private KernelCpuUidTestBpfMapReader mBpfMapReader;
     private VerifiableCallback mCallback;
 
     private Random mRand = new Random(12345);
+    protected boolean mUseBpf;
     private final int mCpus = 6;
     private final String mHeadline = "policy0: 4 policy4: 2\n";
+    private final long[] mCores = {4, 2};
     private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
 
     private Context getContext() {
         return InstrumentationRegistry.getContext();
     }
 
+    @Parameters(name="useBpf={0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] { {true}, {false} });
+    }
+
+    public KernelCpuUidClusterTimeReaderTest(boolean useBpf) {
+        mUseBpf = useBpf;
+    }
+
     @Before
     public void setUp() {
         mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
         mTestFile = new File(mTestDir, "test.file");
+        mBpfMapReader = new KernelCpuUidTestBpfMapReader();
         mReader = new KernelCpuUidClusterTimeReader(
-                new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+                new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader,
+                false);
         mCallback = new VerifiableCallback();
     }
 
@@ -82,7 +99,7 @@
     @Test
     public void testReadDelta() throws Exception {
         final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times1));
+        setCoresAndData(times1);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], clusterTime(times1[i]));
@@ -92,7 +109,7 @@
         // Verify that a second call will only return deltas.
         mCallback.clear();
         final long[][] times2 = increaseTime(times1);
-        writeToFile(mHeadline + uidLines(mUids, times2));
+        setCoresAndData(times2);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], subtract(clusterTime(times2[i]), clusterTime(times1[i])));
@@ -107,7 +124,7 @@
         // Verify that calling with a null callback doesn't result in any crashes
         mCallback.clear();
         final long[][] times3 = increaseTime(times2);
-        writeToFile(mHeadline + uidLines(mUids, times3));
+        setCoresAndData(times3);
         mReader.readDelta(null);
         mCallback.verifyNoMoreInteractions();
 
@@ -115,19 +132,19 @@
         // the previous call had null callback.
         mCallback.clear();
         final long[][] times4 = increaseTime(times3);
-        writeToFile(mHeadline + uidLines(mUids, times4));
+        setCoresAndData(times4);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], subtract(clusterTime(times4[i]), clusterTime(times3[i])));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCoresAndData();
     }
 
     @Test
     public void testReadAbsolute() throws Exception {
         final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times1));
+        setCoresAndData(times1);
         mReader.readAbsolute(mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], clusterTime(times1[i]));
@@ -137,19 +154,19 @@
         // Verify that a second call should still return absolute values
         mCallback.clear();
         final long[][] times2 = increaseTime(times1);
-        writeToFile(mHeadline + uidLines(mUids, times2));
+        setCoresAndData(times2);
         mReader.readAbsolute(mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], clusterTime(times2[i]));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCoresAndData();
     }
 
     @Test
     public void testReadDeltaDecreasedTime() throws Exception {
         final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times1));
+        setCoresAndData(times1);
         mReader.readDelta(mCallback);
 
         // Verify that there should not be a callback for a particular UID if its time decreases.
@@ -157,19 +174,19 @@
         final long[][] times2 = increaseTime(times1);
         System.arraycopy(times1[0], 0, times2[0], 0, mCpus);
         times2[0][0] = 100;
-        writeToFile(mHeadline + uidLines(mUids, times2));
+        setCoresAndData(times2);
         mReader.readDelta(mCallback);
         for (int i = 1; i < mUids.length; i++) {
             mCallback.verify(mUids[i], subtract(clusterTime(times2[i]), clusterTime(times1[i])));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCoresAndData();
 
         // Verify that the internal state was not modified.
         mCallback.clear();
         final long[][] times3 = increaseTime(times2);
         times3[0] = increaseTime(times1)[0];
-        writeToFile(mHeadline + uidLines(mUids, times3));
+        setCoresAndData(times3);
         mReader.readDelta(mCallback);
         mCallback.verify(mUids[0], subtract(clusterTime(times3[0]), clusterTime(times1[0])));
         for (int i = 1; i < mUids.length; i++) {
@@ -181,26 +198,26 @@
     @Test
     public void testReadDeltaNegativeTime() throws Exception {
         final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
-        writeToFile(mHeadline + uidLines(mUids, times1));
+        setCoresAndData(times1);
         mReader.readDelta(mCallback);
 
         // Verify that there should not be a callback for a particular UID if its time decreases.
         mCallback.clear();
         final long[][] times2 = increaseTime(times1);
         times2[0][0] *= -1;
-        writeToFile(mHeadline + uidLines(mUids, times2));
+        setCoresAndData(times2);
         mReader.readDelta(mCallback);
         for (int i = 1; i < mUids.length; i++) {
             mCallback.verify(mUids[i], subtract(clusterTime(times2[i]), clusterTime(times1[i])));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearCoresAndData();
 
         // Verify that the internal state was not modified.
         mCallback.clear();
         final long[][] times3 = increaseTime(times2);
         times3[0] = increaseTime(times1)[0];
-        writeToFile(mHeadline + uidLines(mUids, times3));
+        setCoresAndData(times3);
         mReader.readDelta(mCallback);
         mCallback.verify(mUids[0], subtract(clusterTime(times3[0]), clusterTime(times1[0])));
         for (int i = 1; i < mUids.length; i++) {
@@ -209,6 +226,28 @@
         mCallback.verifyNoMoreInteractions();
     }
 
+    private void setCoresAndData(long[][] times) throws IOException {
+        if (mUseBpf) {
+            mBpfMapReader.setClusterCores(mCores);
+            SparseArray<long[]> data = new SparseArray<>();
+            for (int i = 0; i < mUids.length; i++) {
+                data.put(mUids[i], times[i]);
+            }
+            mBpfMapReader.setData(data);
+        } else {
+            writeToFile(mHeadline + uidLines(mUids, times));
+        }
+    }
+
+    private void clearCoresAndData() {
+        if (mUseBpf) {
+            mBpfMapReader.setClusterCores(null);
+            mBpfMapReader.setData(new SparseArray<>());
+        } else {
+            assertTrue(mTestFile.delete());
+        }
+    }
+
     private long[] clusterTime(long[] times) {
         // Assumes 4 + 2 cores
         return new long[]{times[0] + times[1] / 2 + times[2] / 3 + times[3] / 4,
@@ -277,4 +316,36 @@
             assertEquals(0, mData.size());
         }
     }
+
+    private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+        private long[] mClusterCores;
+        private SparseArray<long[]> mNewData = new SparseArray<>();
+
+        public void setData(SparseArray<long[]> data) {
+            mNewData = data;
+        }
+
+        public void setClusterCores(long[] cores) {
+            mClusterCores = cores;
+        }
+
+        @Override
+        public final boolean startTrackingBpfTimes() {
+            return true;
+        }
+
+        @Override
+        protected final boolean readBpfData() {
+            if (!mUseBpf) {
+                return false;
+            }
+            mData = mNewData;
+            return true;
+        }
+
+        @Override
+        public final long[] getDataDimensions() {
+            return mClusterCores;
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
index 0b6fed3..c60a6d6 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java
@@ -29,7 +29,6 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
 
@@ -37,6 +36,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -45,6 +46,7 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Random;
 
 /**
@@ -53,14 +55,16 @@
  * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidFreqTimeReaderTest
  */
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class KernelCpuUidFreqTimeReaderTest {
     private File mTestDir;
     private File mTestFile;
     private KernelCpuUidFreqTimeReader mReader;
+    private KernelCpuUidTestBpfMapReader mBpfMapReader;
     private VerifiableCallback mCallback;
     @Mock
     private PowerProfile mPowerProfile;
+    private boolean mUseBpf;
 
     private Random mRand = new Random(12345);
     private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
@@ -69,13 +73,23 @@
         return InstrumentationRegistry.getContext();
     }
 
+    @Parameters(name="useBpf={0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] { {true}, {false} });
+    }
+
+    public KernelCpuUidFreqTimeReaderTest(boolean useBpf) {
+        mUseBpf = useBpf;
+    }
+
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
         mTestFile = new File(mTestDir, "test.file");
+        mBpfMapReader = new KernelCpuUidTestBpfMapReader();
         mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
-                new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+                new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
         mCallback = new VerifiableCallback();
     }
 
@@ -97,17 +111,17 @@
         final int[][] numFreqs = {{3, 6}, {4, 5}, {3, 5, 4}, {3}};
         for (int i = 0; i < freqs.length; ++i) {
             mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
-                    new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+                    new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
             setCpuClusterFreqs(numClusters[i], numFreqs[i]);
-            writeToFile(freqsLine(freqs[i]));
+            setFreqs(freqs[i]);
             long[] actualFreqs = mReader.readFreqs(mPowerProfile);
             assertArrayEquals(freqs[i], actualFreqs);
             final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
                     Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
             assertFalse(errMsg, mReader.perClusterTimesAvailable());
 
-            // Verify that a second call won't read the proc file again
-            assertTrue(mTestFile.delete());
+            // Verify that a second call won't re-read the freqs
+            clearFreqsAndData();
             actualFreqs = mReader.readFreqs(mPowerProfile);
             assertArrayEquals(freqs[i], actualFreqs);
             assertFalse(errMsg, mReader.perClusterTimesAvailable());
@@ -125,17 +139,17 @@
         final int[][] numFreqs = {{4}, {3, 5}, {3, 5, 4}};
         for (int i = 0; i < freqs.length; ++i) {
             mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
-                    new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
+                    new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false);
             setCpuClusterFreqs(numClusters[i], numFreqs[i]);
-            writeToFile(freqsLine(freqs[i]));
+            setFreqs(freqs[i]);
             long[] actualFreqs = mReader.readFreqs(mPowerProfile);
             assertArrayEquals(freqs[i], actualFreqs);
             final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
                     Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
             assertTrue(errMsg, mReader.perClusterTimesAvailable());
 
-            // Verify that a second call won't read the proc file again
-            assertTrue(mTestFile.delete());
+            // Verify that a second call won't re-read the freqs
+            clearFreqsAndData();
             actualFreqs = mReader.readFreqs(mPowerProfile);
             assertArrayEquals(freqs[i], actualFreqs);
             assertTrue(errMsg, mReader.perClusterTimesAvailable());
@@ -147,7 +161,7 @@
         final long[] freqs = {110, 123, 145, 167, 289, 997};
         final long[][] times = increaseTime(new long[mUids.length][freqs.length]);
 
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times));
+        setFreqsAndData(freqs, times);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], times[i]);
@@ -155,14 +169,14 @@
         mCallback.verifyNoMoreInteractions();
 
         // Verify that readDelta also reads the frequencies if not already available.
-        assertTrue(mTestFile.delete());
+        clearFreqsAndData();
         long[] actualFreqs = mReader.readFreqs(mPowerProfile);
         assertArrayEquals(freqs, actualFreqs);
 
         // Verify that a second call will only return deltas.
         mCallback.clear();
         final long[][] newTimes1 = increaseTime(times);
-        writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes1));
+        setFreqsAndData(freqs, newTimes1);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], subtract(newTimes1[i], times[i]));
@@ -177,7 +191,7 @@
         // Verify that calling with a null callback doesn't result in any crashes
         mCallback.clear();
         final long[][] newTimes2 = increaseTime(newTimes1);
-        writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes2));
+        setFreqsAndData(freqs, newTimes2);
         mReader.readDelta(null);
         mCallback.verifyNoMoreInteractions();
 
@@ -185,13 +199,13 @@
         // the previous call had null callback.
         mCallback.clear();
         final long[][] newTimes3 = increaseTime(newTimes2);
-        writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes3));
+        setFreqsAndData(freqs, newTimes3);
         mReader.readDelta(mCallback);
         for (int i = 0; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], subtract(newTimes3[i], newTimes2[i]));
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearFreqsAndData();
     }
 
     @Test
@@ -199,7 +213,7 @@
         final long[] freqs = {110, 123, 145, 167, 289, 997};
         final long[][] times1 = increaseTime(new long[mUids.length][freqs.length]);
 
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times1));
+        setFreqsAndData(freqs, times1);
         mReader.readAbsolute(mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], times1[i]);
@@ -207,20 +221,20 @@
         mCallback.verifyNoMoreInteractions();
 
         // Verify that readDelta also reads the frequencies if not already available.
-        assertTrue(mTestFile.delete());
+        clearFreqsAndData();
         long[] actualFreqs = mReader.readFreqs(mPowerProfile);
         assertArrayEquals(freqs, actualFreqs);
 
         // Verify that a second call should still return absolute values
         mCallback.clear();
         final long[][] times2 = increaseTime(times1);
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times2));
+        setFreqsAndData(freqs, times2);
         mReader.readAbsolute(mCallback);
         for (int i = 0; i < mUids.length; i++) {
             mCallback.verify(mUids[i], times2[i]);
         }
         mCallback.verifyNoMoreInteractions();
-        assertTrue(mTestFile.delete());
+        clearFreqsAndData();
     }
 
     @Test
@@ -228,14 +242,14 @@
         final long[] freqs = {110, 123, 145, 167, 289, 997};
         final long[][] times1 = increaseTime(new long[mUids.length][freqs.length]);
 
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times1));
+        setFreqsAndData(freqs, times1);
         mReader.readDelta(mCallback);
 
         // Verify that there should not be a callback for a particular UID if its time decreases.
         mCallback.clear();
         final long[][] times2 = increaseTime(times1);
         times2[0][0] = 1000;
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times2));
+        setFreqsAndData(freqs, times2);
         mReader.readDelta(mCallback);
         for (int i = 1; i < mUids.length; i++) {
             mCallback.verify(mUids[i], subtract(times2[i], times1[i]));
@@ -246,7 +260,7 @@
         mCallback.clear();
         final long[][] times3 = increaseTime(times2);
         times3[0] = increaseTime(times1)[0];
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times3));
+        setFreqsAndData(freqs, times3);
         mReader.readDelta(mCallback);
         mCallback.verify(mUids[0], subtract(times3[0], times1[0]));
         for (int i = 1; i < mUids.length; i++) {
@@ -258,7 +272,7 @@
         mCallback.clear();
         final long[][] times4 = increaseTime(times3);
         times4[0][0] *= -1;
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times4));
+        setFreqsAndData(freqs, times4);
         mReader.readDelta(mCallback);
         for (int i = 1; i < mUids.length; ++i) {
             mCallback.verify(mUids[i], subtract(times4[i], times3[i]));
@@ -269,14 +283,44 @@
         mCallback.clear();
         final long[][] times5 = increaseTime(times4);
         times5[0] = increaseTime(times3)[0];
-        writeToFile(freqsLine(freqs) + uidLines(mUids, times5));
+        setFreqsAndData(freqs, times5);
         mReader.readDelta(mCallback);
         mCallback.verify(mUids[0], subtract(times5[0], times3[0]));
         for (int i = 1; i < mUids.length; i++) {
             mCallback.verify(mUids[i], subtract(times5[i], times4[i]));
         }
 
-        assertTrue(mTestFile.delete());
+        clearFreqsAndData();
+    }
+
+    private void setFreqs(long[] freqs) throws IOException {
+        if (mUseBpf) {
+            mBpfMapReader.setFreqs(freqs);
+        } else {
+            writeToFile(freqsLine(freqs));
+        }
+    }
+
+    private void setFreqsAndData(long[] freqs, long[][] times) throws IOException {
+        if (mUseBpf) {
+            mBpfMapReader.setFreqs(freqs);
+            SparseArray<long[]> data = new SparseArray<>();
+            for (int i = 0; i < mUids.length; i++) {
+                data.put(mUids[i], times[i]);
+            }
+            mBpfMapReader.setData(data);
+        } else {
+            writeToFile(freqsLine(freqs) + uidLines(mUids, times));
+        }
+    }
+
+    private void clearFreqsAndData() {
+        if (mUseBpf) {
+            mBpfMapReader.setFreqs(null);
+            mBpfMapReader.setData(new SparseArray<>());
+        } else {
+            assertTrue(mTestFile.delete());
+        }
     }
 
     private String freqsLine(long[] freqs) {
@@ -358,4 +402,36 @@
             assertEquals(0, mData.size());
         }
     }
+
+    private class KernelCpuUidTestBpfMapReader extends KernelCpuUidBpfMapReader {
+        private long[] mCpuFreqs;
+        private SparseArray<long[]> mNewData = new SparseArray<>();
+
+        public void setData(SparseArray<long[]> data) {
+            mNewData = data;
+        }
+
+        public void setFreqs(long[] freqs) {
+            mCpuFreqs = freqs;
+        }
+
+        @Override
+        public final boolean startTrackingBpfTimes() {
+            return true;
+        }
+
+        @Override
+        protected final boolean readBpfData() {
+            if (!mUseBpf) {
+                return false;
+            }
+            mData = mNewData;
+            return true;
+        }
+
+        @Override
+        public final long[] getDataDimensions() {
+            return mCpuFreqs;
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
index 479e19e..dac35e5 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
@@ -31,20 +31,33 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.Arrays;
+import java.util.Collection;
 
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(Parameterized.class)
 public class KernelSingleUidTimeReaderTest {
     private final static int TEST_UID = 2222;
     private final static int TEST_FREQ_COUNT = 5;
 
     private KernelSingleUidTimeReader mReader;
     private TestInjector mInjector;
+    protected boolean mUseBpf;
+
+    @Parameters(name="useBpf={0}")
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] { {true}, {false} });
+    }
+
+    public KernelSingleUidTimeReaderTest(boolean useBpf) {
+        mUseBpf = useBpf;
+    }
 
     @Before
     public void setUp() {
@@ -273,6 +286,7 @@
 
     class TestInjector extends Injector {
         private byte[] mData;
+        private long[] mBpfData;
         private boolean mThrowExcpetion;
 
         @Override
@@ -284,6 +298,14 @@
             }
         }
 
+        @Override
+        public long[] readBpfData(int uid) {
+            if (!mUseBpf || mBpfData == null) {
+                return new long[0];
+            }
+            return mBpfData;
+        }
+
         public void setData(long[] cpuTimes) {
             final ByteBuffer buffer = ByteBuffer.allocate(cpuTimes.length * Long.BYTES);
             buffer.order(ByteOrder.nativeOrder());
@@ -291,6 +313,7 @@
                 buffer.putLong(time / 10);
             }
             mData = buffer.array();
+            mBpfData = cpuTimes.clone();
         }
 
         public void letReadDataThrowException(boolean throwException) {
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 9c2e95f..c1e7a36 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -2243,6 +2243,19 @@
         return nativeCreateGraphicBufferHandle(mNativePtr);
     }
 
+    /**
+     * @return {@link HardwareBuffer} which is internally used by hardware bitmap
+     *
+     * Note: the HardwareBuffer does *not* have an associated {@link ColorSpace}.
+     * To render this object the same as its rendered with this Bitmap, you
+     * should also call {@link getColorSpace}.
+     *
+     * @hide
+     */
+    public HardwareBuffer getHardwareBuffer() {
+        return nativeGetHardwareBuffer(mNativePtr);
+    }
+
     //////////// native methods
 
     private static native Bitmap nativeCreate(int[] colors, int offset,
@@ -2308,6 +2321,7 @@
     private static native Bitmap nativeWrapHardwareBufferBitmap(HardwareBuffer buffer,
                                                                 long nativeColorSpace);
     private static native GraphicBuffer nativeCreateGraphicBufferHandle(long nativeBitmap);
+    private static native HardwareBuffer nativeGetHardwareBuffer(long nativeBitmap);
     private static native ColorSpace nativeComputeColorSpace(long nativePtr);
     private static native void nativeSetColorSpace(long nativePtr, long nativeColorSpace);
     private static native boolean nativeIsSRGB(long nativePtr);
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index cefc9db..3a771bb 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -19,14 +19,20 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.hardware.cas.V1_0.HidlCasPluginDescriptor;
 import android.hardware.cas.V1_0.ICas;
 import android.hardware.cas.V1_0.IMediaCasService;
 import android.hardware.cas.V1_2.ICasListener;
+import android.hardware.cas.V1_2.Status;
 import android.media.MediaCasException.*;
 import android.media.tv.TvInputService.PriorityHintUseCaseType;
+import android.media.tv.tunerresourcemanager.CasSessionRequest;
+import android.media.tv.tunerresourcemanager.ResourceClientProfile;
+import android.media.tv.tunerresourcemanager.TunerResourceManager;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.IHwBinder;
 import android.os.Looper;
@@ -39,6 +45,9 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
 
 /**
  * MediaCas can be used to obtain keys for descrambling protected media streams, in
@@ -110,6 +119,10 @@
     private EventHandler mEventHandler;
     private @PriorityHintUseCaseType int mPriorityHint;
     private String mTvInputServiceSessionId;
+    private int mClientId;
+    private int mCasSystemId;
+    private TunerResourceManager mTunerResourceManager = null;
+    private final Map<Session, Integer> mSessionMap = new HashMap<>();
 
     /**
      * Scrambling modes used to open cas sessions.
@@ -329,6 +342,10 @@
                         createFromSessionId(sessionId), msg.arg1, msg.arg2,
                         bundle.getByteArray(DATA_KEY));
             } else if (msg.what == MSG_CAS_STATUS_EVENT) {
+                if ((msg.arg1 == PLUGIN_STATUS_SESSION_NUMBER_CHANGED)
+                        && (mTunerResourceManager != null)) {
+                    mTunerResourceManager.updateCasInfo(mCasSystemId, msg.arg2);
+                }
                 mListener.onPluginStatusUpdate(MediaCas.this, msg.arg1, msg.arg2);
             } else if (msg.what == MSG_CAS_RESOURCE_LOST) {
                 mListener.onResourceLost(MediaCas.this);
@@ -364,6 +381,19 @@
                     EventHandler.MSG_CAS_STATUS_EVENT, status, arg));
         }
     };
+
+    private final TunerResourceManager.ResourcesReclaimListener mResourceListener =
+            new TunerResourceManager.ResourcesReclaimListener() {
+            @Override
+            public void onReclaimResources() {
+                synchronized (mSessionMap) {
+                    mSessionMap.forEach((casSession, sessionResourceId) -> casSession.close());
+                }
+                mEventHandler.sendMessage(mEventHandler.obtainMessage(
+                        EventHandler.MSG_CAS_RESOURCE_LOST));
+            }
+        };
+
     /**
      * Describe a CAS plugin with its CA_system_ID and string name.
      *
@@ -429,11 +459,21 @@
      */
     public final class Session implements AutoCloseable {
         final ArrayList<Byte> mSessionId;
+        boolean mIsClosed = false;
 
         Session(@NonNull ArrayList<Byte> sessionId) {
             mSessionId = new ArrayList<Byte>(sessionId);
         }
 
+        private void validateSessionInternalStates() {
+            if (mICas == null) {
+                throw new IllegalStateException();
+            }
+            if (mIsClosed) {
+                MediaCasStateException.throwExceptionIfNeeded(Status.ERROR_CAS_SESSION_NOT_OPENED);
+            }
+        }
+
         /**
          * Query if an object equal current Session object.
          *
@@ -459,7 +499,7 @@
          */
         public void setPrivateData(@NonNull byte[] data)
                 throws MediaCasException {
-            validateInternalStates();
+            validateSessionInternalStates();
 
             try {
                 MediaCasException.throwExceptionIfNeeded(
@@ -483,7 +523,7 @@
          */
         public void processEcm(@NonNull byte[] data, int offset, int length)
                 throws MediaCasException {
-            validateInternalStates();
+            validateSessionInternalStates();
 
             try {
                 MediaCasException.throwExceptionIfNeeded(
@@ -522,7 +562,7 @@
          */
         public void sendSessionEvent(int event, int arg, @Nullable byte[] data)
                 throws MediaCasException {
-            validateInternalStates();
+            validateSessionInternalStates();
 
             if (mICasV11 == null) {
                 Log.d(TAG, "Send Session Event isn't supported by cas@1.0 interface");
@@ -546,7 +586,7 @@
          */
         @NonNull
         public byte[] getSessionId() {
-            validateInternalStates();
+            validateSessionInternalStates();
             return toBytes(mSessionId);
         }
 
@@ -558,11 +598,12 @@
          */
         @Override
         public void close() {
-            validateInternalStates();
-
+            validateSessionInternalStates();
             try {
                 MediaCasStateException.throwExceptionIfNeeded(
                         mICas.closeSession(mSessionId));
+                mIsClosed = true;
+                removeSessionFromResourceMap(this);
             } catch (RemoteException e) {
                 cleanupAndRethrowIllegalState();
             }
@@ -664,6 +705,7 @@
     /**
      * Instantiate a CA system of the specified system id.
      *
+     * @param context the context of the caller.
      * @param casSystemId The system id of the CA system.
      * @param tvInputServiceSessionId The Id of the session opened in TV Input Service (TIS)
      *        {@link android.media.tv.TvInputService#onCreateSession(String, String)}
@@ -672,11 +714,23 @@
      * @throws UnsupportedCasException if the device does not support the
      * specified CA system.
      */
-    public MediaCas(int casSystemId, @Nullable String tvInputServiceSessionId,
-            @PriorityHintUseCaseType int priorityHint)  throws UnsupportedCasException {
+    public MediaCas(@NonNull Context context, int casSystemId,
+            @Nullable String tvInputServiceSessionId,
+            @PriorityHintUseCaseType int priorityHint) throws UnsupportedCasException {
         this(casSystemId);
-        mPriorityHint = priorityHint;
-        mTvInputServiceSessionId = tvInputServiceSessionId;
+
+        Objects.requireNonNull(context, "context must not be null");
+        mCasSystemId = casSystemId;
+        mTunerResourceManager = (TunerResourceManager)
+                context.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
+        if (mTunerResourceManager != null) {
+            int[] clientId = new int[1];
+            ResourceClientProfile profile =
+                    new ResourceClientProfile(tvInputServiceSessionId, priorityHint);
+            mTunerResourceManager.registerClientProfile(
+                    profile, new HandlerExecutor(mEventHandler), mResourceListener, clientId);
+            mClientId = clientId[0];
+        }
     }
 
     IHwBinder getBinder() {
@@ -813,6 +867,40 @@
         }
     }
 
+    private int getSessionResourceId() throws MediaCasException {
+        validateInternalStates();
+
+        int[] sessionResourceId = new int[1];
+        sessionResourceId[0] = -1;
+        if (mTunerResourceManager != null) {
+            CasSessionRequest casSessionRequest = new CasSessionRequest(mClientId, mCasSystemId);
+            if (!mTunerResourceManager.requestCasSession(casSessionRequest, sessionResourceId)) {
+                throw new MediaCasException.ResourceBusyException(
+                    "insufficient resource to Open Session");
+            }
+        }
+        return  sessionResourceId[0];
+    }
+
+    private void addSessionToResourceMap(Session session, int sessionResourceId) {
+
+        if (sessionResourceId != -1) {
+            synchronized (mSessionMap) {
+                mSessionMap.put(session, sessionResourceId);
+            }
+        }
+    }
+
+    private void removeSessionFromResourceMap(Session session) {
+
+        synchronized (mSessionMap) {
+            if (mSessionMap.get(session) != null) {
+                mTunerResourceManager.releaseCasSession(mSessionMap.get(session));
+                mSessionMap.remove(session);
+            }
+        }
+    }
+
     /**
      * Open a session to descramble one or more streams scrambled by the
      * conditional access system.
@@ -824,12 +912,13 @@
      * @throws MediaCasStateException for CAS-specific state exceptions.
      */
     public Session openSession() throws MediaCasException {
-        validateInternalStates();
+        int sessionResourceId = getSessionResourceId();
 
         try {
             OpenSessionCallback cb = new OpenSessionCallback();
             mICas.openSession(cb);
             MediaCasException.throwExceptionIfNeeded(cb.mStatus);
+            addSessionToResourceMap(cb.mSession, sessionResourceId);
             return cb.mSession;
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
@@ -853,7 +942,7 @@
     @Nullable
     public Session openSession(@SessionUsage int sessionUsage, @ScramblingMode int scramblingMode)
             throws MediaCasException {
-        validateInternalStates();
+        int sessionResourceId = getSessionResourceId();
 
         if (mICasV12 == null) {
             Log.d(TAG, "Open Session with scrambling mode is only supported by cas@1.2+ interface");
@@ -864,6 +953,7 @@
             OpenSession_1_2_Callback cb = new OpenSession_1_2_Callback();
             mICasV12.openSession_1_2(sessionUsage, scramblingMode, cb);
             MediaCasException.throwExceptionIfNeeded(cb.mStatus);
+            addSessionToResourceMap(cb.mSession, sessionResourceId);
             return cb.mSession;
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
@@ -987,6 +1077,16 @@
                 mICas = null;
             }
         }
+
+        if (mTunerResourceManager != null) {
+            mTunerResourceManager.unregisterClientProfile(mClientId);
+            mTunerResourceManager = null;
+        }
+
+        if (mHandlerThread != null) {
+            mHandlerThread.quit();
+            mHandlerThread = null;
+        }
     }
 
     @Override
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 62c7e51..b579144 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -111,37 +111,32 @@
     public @interface PriorityHintUseCaseType {}
 
     /**
-     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(int, String , int)}:
-     * Background.
-     * TODO Link: Tuner#Tuner(Context, string, int).
+     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String,
+     * int)}: Background. TODO Link: Tuner#Tuner(Context, string, int).
      */
     public static final int PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND = 100;
 
     /**
-     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(int, String , int)}:
-     * Scan.
-     * TODO Link: Tuner#Tuner(Context, string, int).
+     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String,
+     * int)}: Scan. TODO Link: Tuner#Tuner(Context, string, int).
      */
     public static final int PRIORITY_HINT_USE_CASE_TYPE_SCAN = 200;
 
     /**
-     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(int, String , int)}:
-     * Playback.
-     * TODO Link: Tuner#Tuner(Context, string, int).
+     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String,
+     * int)}: Playback. TODO Link: Tuner#Tuner(Context, string, int).
      */
     public static final int PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK = 300;
 
     /**
-     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(int, String , int)}:
-     * Live.
-     * TODO Link: Tuner#Tuner(Context, string, int).
+     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String,
+     * int)}: Live. TODO Link: Tuner#Tuner(Context, string, int).
      */
     public static final int PRIORITY_HINT_USE_CASE_TYPE_LIVE = 400;
 
     /**
-     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(int, String , int)}:
-     * Record.
-     * TODO Link: Tuner#Tuner(Context, string, int).
+     * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String,
+     * int)}: Record. TODO Link: Tuner#Tuner(Context, string, int).
      */
     public static final int PRIORITY_HINT_USE_CASE_TYPE_RECORD = 500;
 
diff --git a/media/java/android/media/tv/tuner/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java
index 364516c..0f5bf08 100644
--- a/media/java/android/media/tv/tuner/DemuxCapabilities.java
+++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java
@@ -18,7 +18,7 @@
 
 import android.annotation.BytesLong;
 import android.annotation.IntDef;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
 import android.annotation.Size;
 import android.annotation.SystemApi;
 import android.media.tv.tuner.filter.Filter;
@@ -159,7 +159,7 @@
      * {@link FilterConfiguration}.
      * <p>The ith element represents the filter's capability as the source for the ith type.
      */
-    @Nullable
+    @NonNull
     @Size(5)
     public int[] getLinkCapabilities() {
         return mLinkCaps;
diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java
index f46d3ce..40add56 100644
--- a/media/java/android/media/tv/tuner/Descrambler.java
+++ b/media/java/android/media/tv/tuner/Descrambler.java
@@ -17,12 +17,15 @@
 package android.media.tv.tuner;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.media.tv.tuner.TunerConstants.Result;
 import android.media.tv.tuner.filter.Filter;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
 /**
  * This class is used to interact with descramblers.
@@ -75,6 +78,7 @@
      * @param filter an optional filter instance to identify upper stream.
      * @return result status of the operation.
      */
+    @Result
     public int addPid(@PidType int pidType, int pid, @Nullable Filter filter) {
         return nativeAddPid(pidType, pid, filter);
     }
@@ -89,6 +93,7 @@
      * @param filter an optional filter instance to identify upper stream.
      * @return result status of the operation.
      */
+    @Result
     public int removePid(@PidType int pidType, int pid, @Nullable Filter filter) {
         return nativeRemovePid(pidType, pid, filter);
     }
@@ -102,7 +107,9 @@
      * @param keyToken the token to be used to link the key slot.
      * @return result status of the operation.
      */
-    public int setKeyToken(@Nullable byte[] keyToken) {
+    @Result
+    public int setKeyToken(@NonNull byte[] keyToken) {
+        Objects.requireNonNull(keyToken, "key token must not be null");
         return nativeSetKeyToken(keyToken);
     }
 
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index bd00201..012b8d6 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -38,6 +38,7 @@
 import android.media.tv.tuner.frontend.FrontendInfo;
 import android.media.tv.tuner.frontend.FrontendSettings;
 import android.media.tv.tuner.frontend.FrontendStatus;
+import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType;
 import android.media.tv.tuner.frontend.OnTuneEventListener;
 import android.media.tv.tuner.frontend.ScanCallback;
 import android.os.Handler;
@@ -45,6 +46,7 @@
 import android.os.Message;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -88,6 +90,10 @@
     private ScanCallback mScanCallback;
     @Nullable
     private Executor mScanCallbackExecutor;
+    @Nullable
+    private OnResourceLostListener mOnResourceLostListener;
+    @Nullable
+    private Executor mOnResourceLostListenerExecutor;
 
     /**
      * Constructs a Tuner instance.
@@ -97,14 +103,37 @@
      * @param useCase the use case of this Tuner instance.
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
-    public Tuner(@NonNull Context context, @NonNull String tvInputSessionId,
-            @TvInputService.PriorityHintUseCaseType int useCase,
-            @Nullable OnResourceLostListener listener) {
+    public Tuner(@NonNull Context context, @Nullable String tvInputSessionId,
+            @TvInputService.PriorityHintUseCaseType int useCase) {
         nativeSetup();
         mContext = context;
     }
 
     /**
+     * Sets the listener for resource lost.
+     *
+     * @param executor the executor on which the listener should be invoked.
+     * @param listener the listener that will be run.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    public void setResourceLostListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull OnResourceLostListener listener) {
+        Objects.requireNonNull(executor, "OnResourceLostListener must not be null");
+        Objects.requireNonNull(listener, "executor must not be null");
+        mOnResourceLostListener = listener;
+        mOnResourceLostListenerExecutor = executor;
+    }
+
+    /**
+     * Removes the listener for resource lost.
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+    public void clearResourceLostListener() {
+        mOnResourceLostListener = null;
+        mOnResourceLostListenerExecutor = null;
+    }
+
+    /**
      * Shares the frontend resource with another Tuner instance
      *
      * @param tuner the Tuner instance to share frontend resource with.
@@ -387,7 +416,7 @@
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
     @Result
-    public int setLna(boolean enable) {
+    public int setLnaEnabled(boolean enable) {
         TunerUtils.checkTunerPermission(mContext);
         return nativeSetLna(enable);
     }
@@ -398,10 +427,10 @@
      * <p>This retrieve the statuses of the frontend for given status types.
      *
      * @param statusTypes an array of status types which the caller requests.
-     * @return statuses which response the caller's requests.
+     * @return statuses which response the caller's requests. {@code null} if the operation failed.
      */
     @Nullable
-    public FrontendStatus getFrontendStatus(@NonNull int[] statusTypes) {
+    public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) {
         return nativeGetFrontendStatus(statusTypes);
     }
 
@@ -482,6 +511,10 @@
 
     /**
      * Gets Demux capabilities.
+     *
+     * @param context the context of the caller.
+     * @return A {@link DemuxCapabilities} instance that represents the demux capabilities.
+     *         {@code null} if the operation failed.
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
     @Nullable
@@ -637,13 +670,15 @@
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
     @Nullable
-    public Lnb openLnb(@CallbackExecutor @Nullable Executor executor, @Nullable LnbCallback cb) {
+    public Lnb openLnb(@CallbackExecutor @NonNull Executor executor, @NonNull LnbCallback cb) {
+        Objects.requireNonNull(executor, "executor must not be null");
+        Objects.requireNonNull(cb, "LnbCallback must not be null");
         TunerUtils.checkTunerPermission(mContext);
         return openLnbByName(null, executor, cb);
     }
 
     /**
-     * Opens an LNB (low-noise block downconverter) object.
+     * Opens an LNB (low-noise block downconverter) object specified by the give name.
      *
      * @param name the LNB name.
      * @param executor the executor on which callback will be invoked. The default event handler
@@ -653,8 +688,10 @@
      */
     @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
     @Nullable
-    public Lnb openLnbByName(@Nullable String name, @CallbackExecutor @Nullable Executor executor,
+    public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor,
             @NonNull LnbCallback cb) {
+        Objects.requireNonNull(executor, "executor must not be null");
+        Objects.requireNonNull(cb, "LnbCallback must not be null");
         TunerUtils.checkTunerPermission(mContext);
         // TODO: use resource manager to get LNB ID.
         return new Lnb(0);
@@ -718,8 +755,10 @@
     @Nullable
     public DvrRecorder openDvrRecorder(
             @BytesLong long bufferSize,
-            @CallbackExecutor @Nullable Executor executor,
-            @Nullable OnRecordStatusChangedListener l) {
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OnRecordStatusChangedListener l) {
+        Objects.requireNonNull(executor, "executor must not be null");
+        Objects.requireNonNull(l, "OnRecordStatusChangedListener must not be null");
         TunerUtils.checkTunerPermission(mContext);
         DvrRecorder dvr = nativeOpenDvrRecorder(bufferSize);
         return dvr;
@@ -739,8 +778,10 @@
     @Nullable
     public DvrPlayback openDvrPlayback(
             @BytesLong long bufferSize,
-            @CallbackExecutor @Nullable Executor executor,
-            @Nullable OnPlaybackStatusChangedListener l) {
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OnPlaybackStatusChangedListener l) {
+        Objects.requireNonNull(executor, "executor must not be null");
+        Objects.requireNonNull(l, "OnPlaybackStatusChangedListener must not be null");
         TunerUtils.checkTunerPermission(mContext);
         DvrPlayback dvr = nativeOpenDvrPlayback(bufferSize);
         return dvr;
diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
index 8105c74..63bc248 100644
--- a/media/java/android/media/tv/tuner/frontend/ScanCallback.java
+++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
@@ -53,7 +53,7 @@
     void onInputStreamIds(@NonNull int[] inputStreamIds);
 
     /** Locked signal standard for DVBS. */
-    void onDvbsStandard(@DvbsFrontendSettings.Standard int dvbsStandandard);
+    void onDvbsStandard(@DvbsFrontendSettings.Standard int dvbsStandard);
 
     /** Locked signal standard. for DVBT */
     void onDvbtStandard(@DvbtFrontendSettings.Standard int dvbtStandard);
diff --git a/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java b/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java
index cbfbf77..afdbce0 100644
--- a/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java
+++ b/media/tests/TunerTest/src/com/android/mediatunertest/TunerTest.java
@@ -48,13 +48,13 @@
 
     @Test
     public void testTunerConstructor() throws Exception {
-        Tuner tuner = new Tuner(mContext, "123", 1, null);
+        Tuner tuner = new Tuner(mContext, "123", 1);
         assertNotNull(tuner);
     }
 
     @Test
     public void testOpenDescrambler() throws Exception {
-        Tuner tuner = new Tuner(mContext, "123", 1, null);
+        Tuner tuner = new Tuner(mContext, "123", 1);
         Descrambler descrambler = tuner.openDescrambler();
         assertNotNull(descrambler);
     }
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index 585acfe..a0d5a1b 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -43,9 +43,11 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.volume.CarVolumeDialogComponent;
@@ -74,10 +76,14 @@
 
     @Singleton
     @Provides
-    static HeadsUpManagerPhone provideHeadsUpManagerPhone(Context context,
+    static HeadsUpManagerPhone provideHeadsUpManagerPhone(
+            Context context,
             StatusBarStateController statusBarStateController,
-            KeyguardBypassController bypassController) {
-        return new HeadsUpManagerPhone(context, statusBarStateController, bypassController);
+            KeyguardBypassController bypassController,
+            NotificationGroupManager groupManager,
+            ConfigurationController configurationController) {
+        return new HeadsUpManagerPhone(context, statusBarStateController, bypassController,
+                groupManager, configurationController);
     }
 
     @Binds
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 5af3ad5..a4eada4 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -128,6 +128,7 @@
 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.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -324,6 +325,7 @@
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
             DismissCallbackRegistry dismissCallbackRegistry,
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             /* Car Settings injected components. */
             CarServiceProvider carServiceProvider,
             Lazy<PowerManagerHelper> powerManagerHelperLazy,
@@ -405,7 +407,8 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
-                dismissCallbackRegistry);
+                dismissCallbackRegistry,
+                statusBarTouchableRegionManager);
         mUserSwitcherController = userSwitcherController;
         mScrimController = scrimController;
         mLockscreenLockIconController = lockscreenLockIconController;
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 7f64990..7294965 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -87,6 +87,7 @@
 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.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
 import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneDependenciesModule;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -197,6 +198,7 @@
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
             DismissCallbackRegistry dismissCallbackRegistry,
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             CarServiceProvider carServiceProvider,
             Lazy<PowerManagerHelper> powerManagerHelperLazy,
             FullscreenUserSwitcher fullscreenUserSwitcher,
@@ -277,6 +279,7 @@
                 extensionController,
                 userInfoControllerImpl,
                 dismissCallbackRegistry,
+                statusBarTouchableRegionManager,
                 carServiceProvider,
                 powerManagerHelperLazy,
                 fullscreenUserSwitcher,
diff --git a/packages/SettingsProvider/res/values-af/strings.xml b/packages/SettingsProvider/res/values-af/strings.xml
index 8c2f8b3..24efbb6 100644
--- a/packages/SettingsProvider/res/values-af/strings.xml
+++ b/packages/SettingsProvider/res/values-af/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Instellingsberging"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Veranderings aan jou warmkolinstellings"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Jou warmkolband het verander."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Hierdie toestel steun nie jou voorkeur vir net 5 GHz nie. Hierdie toestel sal pleks daarvan die 5 GHz-band gebruik wanneer dit beskikbaar is."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Warmkolinstellings het verander"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Tik om besonderhede te sien"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-am/strings.xml b/packages/SettingsProvider/res/values-am/strings.xml
index 640301a..48fb705 100644
--- a/packages/SettingsProvider/res/values-am/strings.xml
+++ b/packages/SettingsProvider/res/values-am/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"የቅንብሮች ማከማቻ"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"በእርስዎ ሆትስፖት ቅንብሮች ላይ ለውጦች"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"የእርስዎ ሆትስፖት ባንድ ተለውጧል።"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ይህ መሣሪያ የእርስዎን ምርጫ ለ5GHz ብቻ አይደግፍም። በምትኩ፣ ይህ መሣሪያ ሲገኝ 5GHz ባንድ ይጠቀማል።"</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"የመገናኛ ነጥብ ቅንብሮች ተለውጠዋል"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"ዝርዝሮችን ለማየት መታ ያድርጉ"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ar/strings.xml b/packages/SettingsProvider/res/values-ar/strings.xml
index 2675839..6371f2c 100644
--- a/packages/SettingsProvider/res/values-ar/strings.xml
+++ b/packages/SettingsProvider/res/values-ar/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"تخزين الإعدادات"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"التغييرات التي طرأت على إعدادات نقطة الاتصال"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"تمّ تغيير نطاق نقطة الاتصال الخاصة بك."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"لا يتوافق هذا الجهاز مع إعدادك المفضّل الخاص باستخدام النطاق 5 غيغاهرتز فقط. وسيستخدم الجهاز بدلاً من ذلك النطاق 5 غيغاهرتز عندما يكون متاحًا."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"تم تغيير إعدادات نقطة الاتصال."</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"انقر للاطّلاع على التفاصيل."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-as/strings.xml b/packages/SettingsProvider/res/values-as/strings.xml
index b70146a..5235e3c 100644
--- a/packages/SettingsProvider/res/values-as/strings.xml
+++ b/packages/SettingsProvider/res/values-as/strings.xml
@@ -20,7 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ছেটিংছসমূহৰ সঞ্চয়াগাৰ"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"আপোনাৰ হটস্পট ছেটিংসমূহত হোৱা সালসলনিসমূহ"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"আপোনাৰ হটস্পটৰ বেণ্ড সলনি কৰা হৈছে।"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"আপোনাৰ কেৱল ৫ গিগাহাৰ্টজৰ প্ৰতি অগ্ৰাধিকাৰ এই ডিভাচইচটোৱে সমৰ্থন নকৰে। ইয়াৰ পৰিৱৰ্তে, ডিভাচইচটোৱে যেতিয়া ৫ গিগাহাৰ্টজ বেণ্ড উপলব্ধ হ’ব তেতিয়া তাক ব্যৱহাৰ কৰিব।"</string>
+    <!-- no translation found for wifi_softap_config_change (5688373762357941645) -->
+    <skip />
+    <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsProvider/res/values-az/strings.xml b/packages/SettingsProvider/res/values-az/strings.xml
index 8b4f75b..b0e8642 100644
--- a/packages/SettingsProvider/res/values-az/strings.xml
+++ b/packages/SettingsProvider/res/values-az/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Ayarlar Deposu"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Hotspot ayarlarınızda dəyişiklik"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Hotspot diapazonu dəyişib."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Bu cihaz yalnız 5GHz üçün tərcihinizi dəstəkləmir. Əvəzində, əlçatan olduqda bu cihaz 5GHz diapazonundan istifadə edəcək."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Hotspot ayarları dəyişib"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Detalları görmək üçün toxunun"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-b+sr+Latn/strings.xml b/packages/SettingsProvider/res/values-b+sr+Latn/strings.xml
index 9c3a00e..def4b68 100644
--- a/packages/SettingsProvider/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsProvider/res/values-b+sr+Latn/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Podešavanja skladišta"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Promene podešavanja za hotspot"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Opseg hotspota je promenjen."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ovaj uređaj ne podržava podešavanje samo za 5 GHz. Uređaj će koristiti opseg od 5 GHz kada bude dostupan."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Podešavanja hotspota su promenjena"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Dodirnite da biste videli detalje"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-be/strings.xml b/packages/SettingsProvider/res/values-be/strings.xml
index 0e098e7..709178e 100644
--- a/packages/SettingsProvider/res/values-be/strings.xml
+++ b/packages/SettingsProvider/res/values-be/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Сховішча налад"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Змяненні ў наладах хот-спота"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Частата хот-спота змянілася."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Прылада не можа працаваць толькі на частаце 5 ГГц. Гэта частата будзе выкарыстоўвацца, калі гэта магчыма."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Налады хот-спота змяніліся"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Дакраніцеся, каб убачыць падрабязныя звесткі"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-bg/strings.xml b/packages/SettingsProvider/res/values-bg/strings.xml
index 30526f2..b2eae73 100644
--- a/packages/SettingsProvider/res/values-bg/strings.xml
+++ b/packages/SettingsProvider/res/values-bg/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Настройки за хранилище"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Промени в настройките ви за точка за достъп"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Честотната лента на точката ви за достъп е променена."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Това устройство не поддържа предпочитанието ви за използване само на честотната лента от 5 ГХц. Вместо това то ще я ползва, когато е възможно."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Настройките за точката за достъп са променени"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Докоснете, за да видите подробности"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-bn/strings.xml b/packages/SettingsProvider/res/values-bn/strings.xml
index 8fc6bbb..c785cd8 100644
--- a/packages/SettingsProvider/res/values-bn/strings.xml
+++ b/packages/SettingsProvider/res/values-bn/strings.xml
@@ -20,7 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"সেটিংস স্টোরেজ"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"আপনার হটস্পট সেটিংসে করা পরিবর্তনগুলি"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"আপনার হটস্পট ব্যান্ড পরিবর্তন করা হয়েছে।"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"এই ডিভাইসটি শুধুমাত্র 5GHz এর জন্য আপনার পছন্দ সমর্থন করে না। পরিবর্তে, এই ডিভাইসটি 5GHz ব্যান্ড ব্যবহার করবে।"</string>
+    <!-- no translation found for wifi_softap_config_change (5688373762357941645) -->
+    <skip />
+    <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsProvider/res/values-bs/strings.xml b/packages/SettingsProvider/res/values-bs/strings.xml
index ddacb32..506fea2 100644
--- a/packages/SettingsProvider/res/values-bs/strings.xml
+++ b/packages/SettingsProvider/res/values-bs/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Postavke za pohranu podataka"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Promjene postavki pristupne tačke"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Opseg pristupne tačke je promijenjen."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ovaj uređaj ne podržava vašu postavku za mreže od isključivo 5 GHz. Uređaj će koristiti opseg of 5 GHz kada bude dostupan."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Postavke pristupne tačke su promijenjene"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Dodirnite da vidite detalje"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ca/strings.xml b/packages/SettingsProvider/res/values-ca/strings.xml
index 0c2ad73..58d3572e 100644
--- a/packages/SettingsProvider/res/values-ca/strings.xml
+++ b/packages/SettingsProvider/res/values-ca/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Configuració de l\'emmagatzematge"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Canvis en la configuració del punt d\'accés Wi‑Fi"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Ha canviat la teva banda del punt d\'accés Wi‑Fi."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Aquest dispositiu no admet utilitzar exclusivament una banda de 5 GHz. El dispositiu utilitzarà una banda de 5 GHz quan estigui disponible."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"La configuració del punt d\'accés Wi‑Fi ha canviat"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Toca per veure\'n els detalls"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-cs/strings.xml b/packages/SettingsProvider/res/values-cs/strings.xml
index ab474b1..449c632 100644
--- a/packages/SettingsProvider/res/values-cs/strings.xml
+++ b/packages/SettingsProvider/res/values-cs/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Paměť pro nastavení"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Změny nastavení hotspotu"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Pásmo hotspotu se změnilo."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Toto zařízení nepodporuje vaše nastavení jen 5GHz pásma. Zařízení použije pásmo 5 GHz, jen když bude dostupné."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Nastavení hotspotu se změnilo"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Klepnutím zobrazíte podrobnosti"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-da/strings.xml b/packages/SettingsProvider/res/values-da/strings.xml
index 719614c..278d978 100644
--- a/packages/SettingsProvider/res/values-da/strings.xml
+++ b/packages/SettingsProvider/res/values-da/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Lagring af indstillinger"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Ændringer af dine indstillinger for hotspot"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Dit hotspotbånd er ændret."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Denne enhed understøtter ikke din præference om kun 5 GHz. Denne enhed vil i stedet bruge 5 GHz-båndet, når det er muligt."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Indstillingerne for hotspots har ændret sig"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Tryk for at se flere oplysninger"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-de/strings.xml b/packages/SettingsProvider/res/values-de/strings.xml
index 6e253e0..a469936 100644
--- a/packages/SettingsProvider/res/values-de/strings.xml
+++ b/packages/SettingsProvider/res/values-de/strings.xml
@@ -20,7 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Einstellungsspeicher"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Änderungen an deinen Hotspot-Einstellungen"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Dein Hotspot-Band hat sich geändert."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Dieses Gerät unterstützt die ausschließliche Nutzung von 5 GHz nicht. Es greift aber immer auf das 5-GHz-Band zurück, wenn dieses verfügbar ist."</string>
+    <!-- no translation found for wifi_softap_config_change (5688373762357941645) -->
+    <skip />
+    <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsProvider/res/values-el/strings.xml b/packages/SettingsProvider/res/values-el/strings.xml
index c47fea2..1bfbf27 100644
--- a/packages/SettingsProvider/res/values-el/strings.xml
+++ b/packages/SettingsProvider/res/values-el/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Αποθηκευτικός χώρος ρυθμίσεων"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Αλλαγές στις ρυθμίσεις σημείου πρόσβασης Wi-Fi"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Το εύρος σημείου πρόσβασης Wi-Fi άλλαξε."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Αυτή η συσκευή δεν υποστηρίζει την προτίμησή σας για αποκλειστική χρήση του εύρους 5 GHz. Αντ\' αυτού, αυτή η συσκευή θα χρησιμοποιεί το εύρος 5 GHz όταν είναι διαθέσιμο."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Οι ρυθμίσεις σημείου πρόσβασης Wi-Fi έχουν αλλάξει"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Πατήστε για προβολή λεπτομερειών."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-en-rAU/strings.xml b/packages/SettingsProvider/res/values-en-rAU/strings.xml
index fac51d83..4e90cad 100644
--- a/packages/SettingsProvider/res/values-en-rAU/strings.xml
+++ b/packages/SettingsProvider/res/values-en-rAU/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Settings Storage"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Changes to your hotspot settings"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Your hotspot band has changed."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"This device doesn’t support your preference for 5 GHz only. Instead, this device will use the 5 GHz band when available."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Hotspot settings have changed"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Tap to see details"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-en-rCA/strings.xml b/packages/SettingsProvider/res/values-en-rCA/strings.xml
index fac51d83..4e90cad 100644
--- a/packages/SettingsProvider/res/values-en-rCA/strings.xml
+++ b/packages/SettingsProvider/res/values-en-rCA/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Settings Storage"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Changes to your hotspot settings"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Your hotspot band has changed."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"This device doesn’t support your preference for 5 GHz only. Instead, this device will use the 5 GHz band when available."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Hotspot settings have changed"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Tap to see details"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-en-rGB/strings.xml b/packages/SettingsProvider/res/values-en-rGB/strings.xml
index fac51d83..4e90cad 100644
--- a/packages/SettingsProvider/res/values-en-rGB/strings.xml
+++ b/packages/SettingsProvider/res/values-en-rGB/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Settings Storage"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Changes to your hotspot settings"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Your hotspot band has changed."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"This device doesn’t support your preference for 5 GHz only. Instead, this device will use the 5 GHz band when available."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Hotspot settings have changed"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Tap to see details"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-en-rIN/strings.xml b/packages/SettingsProvider/res/values-en-rIN/strings.xml
index fac51d83..4e90cad 100644
--- a/packages/SettingsProvider/res/values-en-rIN/strings.xml
+++ b/packages/SettingsProvider/res/values-en-rIN/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Settings Storage"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Changes to your hotspot settings"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Your hotspot band has changed."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"This device doesn’t support your preference for 5 GHz only. Instead, this device will use the 5 GHz band when available."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Hotspot settings have changed"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Tap to see details"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-en-rXC/strings.xml b/packages/SettingsProvider/res/values-en-rXC/strings.xml
index fbc348b..4ea5c57 100644
--- a/packages/SettingsProvider/res/values-en-rXC/strings.xml
+++ b/packages/SettingsProvider/res/values-en-rXC/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‏‏‎‏‏‏‎‏‎‎‏‏‏‎‏‎‏‎‎‎‎‏‏‏‏‏‏‎Settings Storage‎‏‎‎‏‎"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‎‏‏‎‏‏‎‎‎‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‏‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‎‎‎‏‏‎Changes to your hotspot settings‎‏‎‎‏‎"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‎‎‎‎‎‎‎‏‏‎‏‏‏‎‎‏‏‎‎Your hotspot band has changed.‎‏‎‎‏‎"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‎‎‎‎‎‏‏‏‏‎‏‎‏‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‏‎‎This device doesn’t support your preference for 5GHz only. Instead, this device will use the 5GHz band when available.‎‏‎‎‏‎"</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎‎‎‏‎‎‏‎‏‎‎‏‏‏‎‎‏‏‎‎‎‏‏‎‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‎‎‏‏‎‏‎Hotspot settings have changed‎‏‎‎‏‎"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‏‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‏‏‏‎‎‏‏‏‏‎Tap to see details‎‏‎‎‏‎"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-es-rUS/strings.xml b/packages/SettingsProvider/res/values-es-rUS/strings.xml
index af90257..4345b7a 100644
--- a/packages/SettingsProvider/res/values-es-rUS/strings.xml
+++ b/packages/SettingsProvider/res/values-es-rUS/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Almacenamiento de configuración"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Cambios en la configuración de tu hotspot"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Cambió la banda de tu hotspot."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Si bien este dispositivo no admite la opción para conectarse exclusivamente a bandas de 5 GHz, las usará cuando estén disponibles."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Se modificó la configuración de hotspot"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Presiona para obtener más información"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-es/strings.xml b/packages/SettingsProvider/res/values-es/strings.xml
index ff4ee38..3f1fa61 100644
--- a/packages/SettingsProvider/res/values-es/strings.xml
+++ b/packages/SettingsProvider/res/values-es/strings.xml
@@ -20,7 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Almacenamiento de configuración"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Cambios en los ajustes del punto de acceso"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"La banda de tu punto de acceso ha cambiado."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo no admite la opción de conectarse únicamente a bandas de 5 GHz, pero las usará cuando estén disponibles."</string>
+    <!-- no translation found for wifi_softap_config_change (5688373762357941645) -->
+    <skip />
+    <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsProvider/res/values-et/strings.xml b/packages/SettingsProvider/res/values-et/strings.xml
index a0ec593..856ccf1 100644
--- a/packages/SettingsProvider/res/values-et/strings.xml
+++ b/packages/SettingsProvider/res/values-et/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Seadete talletusruum"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Muudatused teie kuumkoha seadetes"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Teie kuumkoha sagedusriba on muutunud."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"See seade ei toeta teie eelistatud ainult 5 GHz riba. Seade kasutab 5 GHz riba ainult siis, kui see on saadaval."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Kuumkoha seaded on muutunud"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Puudutage üksikasjade vaatamiseks"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-eu/strings.xml b/packages/SettingsProvider/res/values-eu/strings.xml
index 220b486..7ca91d8 100644
--- a/packages/SettingsProvider/res/values-eu/strings.xml
+++ b/packages/SettingsProvider/res/values-eu/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Ezarpenen biltegia"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Aldaketak egin dira sare publikoaren ezarpenetan"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Aldatu da sare publikoaren banda."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Gailuak ez du onartzen 5 GHz-ko banda soilik erabiltzeko hobespena. Horren ordez, erabilgarri dagoen bakoitzean erabiliko da 5 GHz-ko banda."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Sare publikoaren ezarpenak aldatu dira"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Sakatu hau xehetasunak ikusteko"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-fa/strings.xml b/packages/SettingsProvider/res/values-fa/strings.xml
index 6819d2f..cc0b557 100644
--- a/packages/SettingsProvider/res/values-fa/strings.xml
+++ b/packages/SettingsProvider/res/values-fa/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"تنظیم محل ذخیره"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"تغییرات در تنظیمات نقطه اتصال"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"نوار نقطه اتصال شما تغییر کرد."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"این دستگاه از اولویت فقط ۵ گیگاهرتز شما پشتیبانی نمی‌کند. هرزمان نوار ۵ گیگاهرتزی دردسترس باشد، این دستگاه از آن استفاده خواهد کرد."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"تنظیمات نقطه اتصال تغییر کرده است"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"برای مشاهده جزئیات ضربه بزنید"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-fi/strings.xml b/packages/SettingsProvider/res/values-fi/strings.xml
index 9ad01eb..829bb3b 100644
--- a/packages/SettingsProvider/res/values-fi/strings.xml
+++ b/packages/SettingsProvider/res/values-fi/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Asetuksien tallennus"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Hotspot-asetustesi muutokset"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Hotspot-taajuutesi on muuttunut."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Tämä laite ei tue asetustasi (vain 5 GHz). Sen sijaan laite käyttää 5 GHz:n taajuutta sen ollessa käytettävissä."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Hotspot-asetuksia on muutettu"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Katso lisätiedot napauttamalla"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-fr-rCA/strings.xml b/packages/SettingsProvider/res/values-fr-rCA/strings.xml
index 62951bd..d640845 100644
--- a/packages/SettingsProvider/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsProvider/res/values-fr-rCA/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Stockage des paramètres"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Modifications apportées à vos paramètres de point d\'accès"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"La bande de votre point d\'accès a changé."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Cet appareil ne prend pas en charge votre préférence pour la bande de 5 GHz seulement. Au lieu de cela, cet appareil utilisera la bande de 5 GHz lorsqu\'elle sera disponible."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Les paramètres de point d\'accès ont changé"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Touchez pour afficher les renseignements"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-fr/strings.xml b/packages/SettingsProvider/res/values-fr/strings.xml
index 56bc65b..ba6ec0b 100644
--- a/packages/SettingsProvider/res/values-fr/strings.xml
+++ b/packages/SettingsProvider/res/values-fr/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Stockage des paramètres"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Modifications apportées à vos paramètres de point d\'accès"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"La bande utilisée par votre point d\'accès a été modifiée."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Cet appareil n\'est pas conçu pour utiliser exclusivement la bande 5 GHz, mais il l\'utilisera chaque fois que disponible."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Les paramètres de point d\'accès ont changé"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Appuyer ici pour plus d\'infos"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-gl/strings.xml b/packages/SettingsProvider/res/values-gl/strings.xml
index 771fade..1be162e 100644
--- a/packages/SettingsProvider/res/values-gl/strings.xml
+++ b/packages/SettingsProvider/res/values-gl/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Almacenamento da configuración"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Cambios na configuración da zona wifi"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Modificouse a banda da zona wifi."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo non admite a opción de conectarse só a bandas de 5 GHz, pero usaraas se están dispoñibles."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"A configuración da zona wifi cambiou"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Toca a notificación para ver os detalles"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-gu/strings.xml b/packages/SettingsProvider/res/values-gu/strings.xml
index a561924..074675f 100644
--- a/packages/SettingsProvider/res/values-gu/strings.xml
+++ b/packages/SettingsProvider/res/values-gu/strings.xml
@@ -20,7 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"સેટિંગ્સ સંગ્રહ"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"તમારા હૉટસ્પૉટ સેટિંગને બદલે છે"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"તમારું હૉટસ્પૉટ બૅન્ડ બદલાઈ ગયું છે."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"આ ડિવાઇસ તમારી ફક્ત 5GHz માટેની પસંદગીને સપોર્ટ કરતું નથી. તેના બદલે, જ્યારે 5GHz બૅન્ડ ઉપલબ્ધ હશે ત્યારે આ ડિવાઇસ તેનો ઉપયોગ કરશે."</string>
+    <!-- no translation found for wifi_softap_config_change (5688373762357941645) -->
+    <skip />
+    <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsProvider/res/values-hi/strings.xml b/packages/SettingsProvider/res/values-hi/strings.xml
index 199a546..9441a59 100644
--- a/packages/SettingsProvider/res/values-hi/strings.xml
+++ b/packages/SettingsProvider/res/values-hi/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"सेटिंग मेमोरी"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"आपकी हॉटस्पॉट की सेटिंग में किए गए बदलाव"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"आपका हॉटस्पॉट का बैंड बदल दिया गया है."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"इस डिवाइस पर आप \'सिर्फ़ 5 गीगाहर्ट्ज़\' वाला विकल्प नहीं चुन सकते. इसके बजाय, 5 गीगाहर्ट्ज़ बैंड उपलब्ध होने पर यह डिवाइस उसका इस्तेमाल करेगा."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"हॉटस्पॉट की सेटिंग बदल गई हैं"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"जानकारी देखने के लिए टैप करें"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-hr/strings.xml b/packages/SettingsProvider/res/values-hr/strings.xml
index 9129a04..206f50f 100644
--- a/packages/SettingsProvider/res/values-hr/strings.xml
+++ b/packages/SettingsProvider/res/values-hr/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Postavke pohrane"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Promjene postavki vaše žarišne točke"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Promijenila se frekvencija vaše žarišne točke."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ovaj uređaj ne podržava vašu postavku za upotrebu samo 5 GHz. Upotrebljavat će frekvenciju od 5 GHz kada je dostupna."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Promijenile su se postavke žarišne točke"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Dodirnite da biste vidjeli pojedinosti"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-hu/strings.xml b/packages/SettingsProvider/res/values-hu/strings.xml
index a1ed494..a7ca0d2 100644
--- a/packages/SettingsProvider/res/values-hu/strings.xml
+++ b/packages/SettingsProvider/res/values-hu/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Beállítástároló"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"A hotspot beállításainak módosítása"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"A hotspot sávja megváltozott."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ez az eszköz nem támogatja a csak 5 GHz-es sávra vonatkozó beállítást. Az eszköz akkor használ 5 GHz-es sávot, ha a sáv rendelkezésre áll."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"A hotspot beállításai módosultak"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Koppintson a részletek megtekintéséhez"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-hy/strings.xml b/packages/SettingsProvider/res/values-hy/strings.xml
index 6d716f8..4fdb880 100644
--- a/packages/SettingsProvider/res/values-hy/strings.xml
+++ b/packages/SettingsProvider/res/values-hy/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Կարգավորումների պահուստ"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Փոփոխություններ թեժ կետի կարգավորումներում"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Ձեր թեժ կետի հաճախականությունը փոխվել է։"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Սարքը չի կարող աշխատել միայն 5 ԳՀց հաճախականությամբ։ Այդ հաճախականությունը կօգտագործվի հնարավորության դեպքում։"</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Թեժ կետի կարգավորումները փոխվել են"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Հպեք՝ ավելին իմանալու համար"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-in/strings.xml b/packages/SettingsProvider/res/values-in/strings.xml
index 8b1b1f4..911892b 100644
--- a/packages/SettingsProvider/res/values-in/strings.xml
+++ b/packages/SettingsProvider/res/values-in/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Setelan Penyimpanan"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Perubahan pada setelan hotspot Anda"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Band hotspot Anda telah berubah."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Perangkat ini tidak mendukung preferensi Anda, yaitu hanya 5GHz. Sebagai gantinya, perangkat ini akan menggunakan band 5GHz jika tersedia."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Setelan hotspot telah berubah"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Ketuk untuk melihat detail"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-is/strings.xml b/packages/SettingsProvider/res/values-is/strings.xml
index 150c084..c72442e 100644
--- a/packages/SettingsProvider/res/values-is/strings.xml
+++ b/packages/SettingsProvider/res/values-is/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Stillingageymsla"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Breytingar á stillingum heits reits"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Tíðnisvið heita reitsins hefur breyst."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Þetta tæki styður ekki val þitt fyrir aðeins 5 GHz. Í staðinn mun þetta tæki nota 5 GHz tíðnisvið þegar það er í boði."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Stillingum heits reits hefur verið breytt"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Ýttu til að sjá upplýsingar"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-it/strings.xml b/packages/SettingsProvider/res/values-it/strings.xml
index 6715c3c..0e11d06 100644
--- a/packages/SettingsProvider/res/values-it/strings.xml
+++ b/packages/SettingsProvider/res/values-it/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Memoria impostazioni"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Modifiche alle tue impostazioni dell\'hotspot"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"La banda dell\'hotspot è cambiata."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Questo dispositivo non supporta la tua preferenza esclusiva per 5 GHz. Utilizzerà la banda a 5 GHz solo quando è disponibile."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Le impostazioni di hotspot sono state modificate"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Tocca per vedere i dettagli"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-iw/strings.xml b/packages/SettingsProvider/res/values-iw/strings.xml
index dd44329..8d8594d 100644
--- a/packages/SettingsProvider/res/values-iw/strings.xml
+++ b/packages/SettingsProvider/res/values-iw/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"אחסון הגדרות"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"שינויים להגדרות של הנקודה לשיתוף אינטרנט"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"התדר של הנקודה לשיתוף אינטרנט השתנה."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"‏מכשיר זה לא תומך בהעדפות שלך ל-5GHz בלבד. במקום זאת, מכשיר זה ישתמש בתדר 5GHz כשיהיה זמין."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"ההגדרות של הנקודה לשיתוף אינטרנט השתנו"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"יש להקיש להצגת פרטים"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ja/strings.xml b/packages/SettingsProvider/res/values-ja/strings.xml
index 7bbcd46..5b91b2d 100644
--- a/packages/SettingsProvider/res/values-ja/strings.xml
+++ b/packages/SettingsProvider/res/values-ja/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ストレージの設定"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"アクセス ポイントの設定の変更"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"アクセス ポイントの帯域幅が変更されました。"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"このデバイスは 5 GHz のみという設定に対応していません。ただし、5 GHz 周波数帯が利用できるときには利用します。"</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"アクセス ポイントの設定が変更されました"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"タップして詳細を確認してください"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ka/strings.xml b/packages/SettingsProvider/res/values-ka/strings.xml
index 86db4f3..70845ac 100644
--- a/packages/SettingsProvider/res/values-ka/strings.xml
+++ b/packages/SettingsProvider/res/values-ka/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"პარამეტრების საცავი"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"თქვენი უსადენო ქსელის პარამეტრების ცვლილება"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"თქვენი უსადენო ქსელის დიაპაზონი შეიცვალა."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ამ მოწყობილობას არ შეუძლია მხოლოდ 5 გჰც სიხშირეზე მუშაობა. აღნიშნული სიხშირის გამოყენება მოხდება მაშინ, როცა ეს შესაძლებელია."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"უსადენო ქსელის პარამეტრები შეიცვალა"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"შეეხეთ დეტალების სანახავად"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-kk/strings.xml b/packages/SettingsProvider/res/values-kk/strings.xml
index a093d08..2823e4e 100644
--- a/packages/SettingsProvider/res/values-kk/strings.xml
+++ b/packages/SettingsProvider/res/values-kk/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Параметрлер жады"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Хотспот параметрлеріне өзгерістер енгізілді"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Хотспот жолағы өзгертілді."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Бұл құрылғы тек 5 ГГц жиілікте жұмыс істей алмайды. Бұл жиілік мүмкін болған жағдайда ғана қолданылады."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Хотспот параметрлері өзгертілді"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Мәліметтерді көру үшін түртіңіз."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-km/strings.xml b/packages/SettingsProvider/res/values-km/strings.xml
index f0a2712..cdae410 100644
--- a/packages/SettingsProvider/res/values-km/strings.xml
+++ b/packages/SettingsProvider/res/values-km/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"កំណត់​ការ​ផ្ទុក"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"ប្ដូរ​ទៅ​ការ​កំណត់​ហតស្ប៉ត​របស់អ្នក"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"កម្រិតបញ្ជូន​ហតស្ប៉ត​របស់​អ្នកបាន​ផ្លាស់ប្ដូរ។"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ឧបករណ៍នេះ​មិនស្គាល់​ចំណូលចិត្ត​របស់អ្នក​សម្រាប់តែ 5GHz ប៉ុណ្ណោះ។ ផ្ទុយមកវិញ ឧបករណ៍នេះ​នឹងប្រើ​កម្រិតបញ្ជូន 5GHz នៅពេល​ដែលអាច​ប្រើបាន។"</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"បាន​ប្ដូរ​ការកំណត់​ហតស្ប៉ត"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"ចុច​ដើម្បី​មើល​ព័ត៌មានលម្អិត"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-kn/strings.xml b/packages/SettingsProvider/res/values-kn/strings.xml
index f2c1d5e..0b0000d 100644
--- a/packages/SettingsProvider/res/values-kn/strings.xml
+++ b/packages/SettingsProvider/res/values-kn/strings.xml
@@ -20,7 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಸಂಗ್ರಹಣೆ"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"ನಿಮ್ಮ ಹಾಟ್‌ಸ್ಪಾಟ್‌ ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿನ ಬದಲಾವಣೆಗಳು"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ನಿಮ್ಮ ಹಾಟ್‌ಸ್ಪಾಟ್‌ ಬ್ಯಾಂಡ್ ಬದಲಾಗಿದೆ."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ಈ ಸಾಧನವು 5GHz ಗೆ ಮಾತ್ರ ನಿಮ್ಮ ಆದ್ಯತೆಯನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ. ಬದಲಿಗೆ, ಈ ಸಾಧನವು 5GHz ಬ್ಯಾಂಡ್ ಅನ್ನು ಲಭ್ಯವಿರುವಾಗ ಬಳಸುತ್ತದೆ."</string>
+    <!-- no translation found for wifi_softap_config_change (5688373762357941645) -->
+    <skip />
+    <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsProvider/res/values-ko/strings.xml b/packages/SettingsProvider/res/values-ko/strings.xml
index 841832d..d76b766 100644
--- a/packages/SettingsProvider/res/values-ko/strings.xml
+++ b/packages/SettingsProvider/res/values-ko/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"설정 저장소"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"핫스팟 설정 변경"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"핫스팟 대역이 변경되었습니다."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"이 기기에서는 5GHz 전용 환경설정이 지원되지 않습니다. 대신 가능할 때만 기기에서 5GHz 대역이 사용됩니다."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"핫스팟 설정 변경됨"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"세부정보를 보려면 탭하세요."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ky/strings.xml b/packages/SettingsProvider/res/values-ky/strings.xml
index 014c66c..e5b82c6 100644
--- a/packages/SettingsProvider/res/values-ky/strings.xml
+++ b/packages/SettingsProvider/res/values-ky/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Жөндөөлөрдү сактоо"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Байланыш түйүнүңүздүн жөндөөлөрү өзгөрүлдү"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Байланыш түйүнүңүздүн жыштыгы өзгөрдү."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Бул түзмөк 5ГГцти гана колдонуу жөндөөсүн колдоого албайт. Анын ордуна, бул түзмөк 5ГГц жыштыгын ал жеткиликтүү болгондо колдонот."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Хотспот жөндөөлөрү өзгөрдү"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Чоо-жайын билүү үчүн басыңыз"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-lo/strings.xml b/packages/SettingsProvider/res/values-lo/strings.xml
index 9d60ba1..c2b5df7 100644
--- a/packages/SettingsProvider/res/values-lo/strings.xml
+++ b/packages/SettingsProvider/res/values-lo/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ບ່ອນເກັບຂໍ້ມູນການຕັ້ງຄ່າ"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"ການປ່ຽນແປງການຕັ້ງຄ່າຮັອດສະປອດຂອງທ່ານ"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ຄື້ນຄວາມຖີ່ຮັອດສະປອດຂອງທ່ານປ່ຽນແປງແລ້ວ."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ອຸປະກອນນີ້ບໍ່ຮອງຮັບການຕັ້ງຄ່າຂອງທ່ານສຳລັບ 5GHz ເທົ່ານັ້ນ. ແຕ່ວ່າອຸປະກອນນີ້ຈະໃຊ້ຄື້ນຄວາມຖີ່ 5GHz ເມື່ອສາມາດໃຊ້ໄດ້."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"ບັນທຶກການຕັ້ງຄ່າຮັອດສະປອດແລ້ວ"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"ແຕະເພື່ອເບິ່ງລາຍລະອຽດ"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-lt/strings.xml b/packages/SettingsProvider/res/values-lt/strings.xml
index 775d3b9..64f429b 100644
--- a/packages/SettingsProvider/res/values-lt/strings.xml
+++ b/packages/SettingsProvider/res/values-lt/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Nustatymų saugykla"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Viešosios interneto prieigos taško nustatymų pakeitimai"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Viešosios prieigos taško dažnio juosta pasikeitė."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Šiame įrenginyje nepalaikoma tik 5 GHz nuostata. Vietoj to šiame įrenginyje bus naudojama 5 GHz dažnio juosta, kai bus pasiekiama."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Buvo pakeisti viešosios interneto prieigos taško nustatymai"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Palieskite ir peržiūrėkite išsamią informaciją"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-lv/strings.xml b/packages/SettingsProvider/res/values-lv/strings.xml
index f7f3117..e5af8f7 100644
--- a/packages/SettingsProvider/res/values-lv/strings.xml
+++ b/packages/SettingsProvider/res/values-lv/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Iestatījumu krātuve"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Izmaiņas tīklāja iestatījumos"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Ir mainīts tīklāja joslas platums."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Šajā ierīcē netiek atbalstīta jūsu preference par tikai 5 GHz joslu. 5 GHz josla ierīcē tiks izmantota, kad tā būs pieejama."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Ir mainīti tīklāja iestatījumi"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Pieskarieties, lai skatītu detalizētāku informāciju."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-mk/strings.xml b/packages/SettingsProvider/res/values-mk/strings.xml
index a245e5f..13ff8a2 100644
--- a/packages/SettingsProvider/res/values-mk/strings.xml
+++ b/packages/SettingsProvider/res/values-mk/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Поставки за меморија"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Промени на поставките за точка на пристап"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Опсегот за точка на пристап е променет."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Уредов не ги поддржува вашите поставки за само 5 GHz. Наместо тоа, ќе го користи опсегот од 5 GHz кога е достапен."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Поставките за точка на пристап се променија"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Допрете за да видите детали"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ml/strings.xml b/packages/SettingsProvider/res/values-ml/strings.xml
index b63f20e..54a05fb 100644
--- a/packages/SettingsProvider/res/values-ml/strings.xml
+++ b/packages/SettingsProvider/res/values-ml/strings.xml
@@ -20,7 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"സംഭരണ ക്രമീകരണം"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"നിങ്ങളുടെ ഹോട്ട്‌സ്‌പോട്ട് ക്രമീകരണത്തിൽ വരുത്തിയ മാറ്റങ്ങൾ"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"നിങ്ങളുടെ ഹോട്ട്‌സ്‌പോട്ട് ബാൻഡ് മാറിയിരിക്കുന്നു."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"നിങ്ങളുടെ മുൻഗണനയനുസരിച്ചുള്ള, 5GHz മാത്രം എന്നത് ഈ ഉപകരണം പിന്തുണയ്ക്കുന്നില്ല. പകരം, 5GHz ബാൻഡ് ലഭ്യമാകുമ്പോൾ അത് ഉപയോഗിക്കും."</string>
+    <!-- no translation found for wifi_softap_config_change (5688373762357941645) -->
+    <skip />
+    <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsProvider/res/values-mn/strings.xml b/packages/SettingsProvider/res/values-mn/strings.xml
index c839177..a9c2e8c 100644
--- a/packages/SettingsProvider/res/values-mn/strings.xml
+++ b/packages/SettingsProvider/res/values-mn/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Тохиргооны Сан"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Таны сүлжээний цэгийн тохиргооны өөрчлөлт"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Таны сүлжээний цэгийн хязгаарыг өөрчилсөн."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Энэ төхөөрөмж нь зөвхөн 5Гц гэсэн таны сонголтыг дэмждэггүй. Оронд нь энэ төхөөрөмж 5Гц-н хязгаарыг боломжтой үед нь ашиглах болно."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Сүлжээний цэгийн тохиргоог өөрчиллөө"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Дэлгэрэнгүй мэдээлэл харахын тулд товшино уу"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-mr/strings.xml b/packages/SettingsProvider/res/values-mr/strings.xml
index 0c7041e..0e80f70 100644
--- a/packages/SettingsProvider/res/values-mr/strings.xml
+++ b/packages/SettingsProvider/res/values-mr/strings.xml
@@ -20,7 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"सेटिंग्ज संचयन"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"तुमच्या हॉटस्पॉट सेटिंग्जमधील बदल"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"तुमचा हॉटस्पॉट बँड बदलला गेला आहे."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"हे डिव्हाइस फक्त ५GHz च्या तुमच्या प्राधान्याला सपोर्ट करत नाही. त्याऐवजी, उपलब्ध असेल तेव्हा हे डिव्हाइस ५GHz बँड वापरेल."</string>
+    <!-- no translation found for wifi_softap_config_change (5688373762357941645) -->
+    <skip />
+    <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsProvider/res/values-ms/strings.xml b/packages/SettingsProvider/res/values-ms/strings.xml
index a1574df..51a8f2b 100644
--- a/packages/SettingsProvider/res/values-ms/strings.xml
+++ b/packages/SettingsProvider/res/values-ms/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Storan Tetapan"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Perubahan kepada tetapan tempat liputan anda"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Jalur tempat liputan anda telah berubah."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Peranti ini tidak menyokong pilihan anda untuk 5GHz sahaja. Sebaliknya, peranti ini akan menggunakan jalur 5GHz apabila tersedia."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Tetapan tempat liputan telah berubah"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Ketik untuk melihat butiran"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-my/strings.xml b/packages/SettingsProvider/res/values-my/strings.xml
index 48d4dba..dc9f531 100644
--- a/packages/SettingsProvider/res/values-my/strings.xml
+++ b/packages/SettingsProvider/res/values-my/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"သိုလှောင်မှုဆက်တင်များ"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"သင်၏ဟော့စပေါ့ ဆက်တင်များ ပြောင်းလဲမှု"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"သင်၏ ဟော့စပေါ့လိုင်း ပြောင်းသွားပါပြီ။"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ဤစက်ပစ္စည်းသည် သင်၏ 5GHz သီးသန့်ရွေးချယ်မှုအတွက် ပံ့ပိုးမထားပါ။ ၎င်းအစား ဤစက်ပစ္စည်းသည် ရနိုင်သည့်အခါ 5GHz လိုင်းကို သုံးသွားပါမည်။"</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"ဟော့စပေါ့ ဆက်တင်များ ပြောင်းသွားပြီ"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"အသေးစိတ်ကြည့်ရန် တို့ပါ"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-nb/strings.xml b/packages/SettingsProvider/res/values-nb/strings.xml
index e0cbd7e..3bdaf83 100644
--- a/packages/SettingsProvider/res/values-nb/strings.xml
+++ b/packages/SettingsProvider/res/values-nb/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Lagring av innstillinger"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Endres til innstillingene dine for Wi-Fi-soner"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Båndet ditt for Wi-Fi-sone er endret."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Denne enheten støtter ikke innstillingen din for bare 5 GHz. I stedet bruker enheten 5 GHz-båndet når det er tilgjengelig."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Innstillingene for Wi-Fi-sone er endret"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Trykk for å se detaljer"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ne/strings.xml b/packages/SettingsProvider/res/values-ne/strings.xml
index 2fd9b00..bb04b6ba 100644
--- a/packages/SettingsProvider/res/values-ne/strings.xml
+++ b/packages/SettingsProvider/res/values-ne/strings.xml
@@ -20,7 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"सेटिङहरू भण्डारण"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"तपाईंका हटस्पट सेटिङहरूमा गरिएका परिवर्तनहरू"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"तपाईंको हटस्पट ब्यान्ड परिवर्तन भएको छ।"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"यो यन्त्रले तपाईंको 5GHz मात्रको प्राथमिकतालाई समर्थन गर्दैन। बरु, उपलब्ध भएको खण्डमा यो यन्त्रले 5GHz ब्यान्ड प्रयोग गर्ने छ।"</string>
+    <!-- no translation found for wifi_softap_config_change (5688373762357941645) -->
+    <skip />
+    <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsProvider/res/values-nl/strings.xml b/packages/SettingsProvider/res/values-nl/strings.xml
index 0b843ae..91b8542 100644
--- a/packages/SettingsProvider/res/values-nl/strings.xml
+++ b/packages/SettingsProvider/res/values-nl/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Opslagruimte voor instellingen"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Wijzigingen in je hotspot-instellingen"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Je hotspot-band is gewijzigd."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Dit apparaat biedt geen ondersteuning voor je voorkeur voor alleen 5 GHz. In plaats daarvan gebruikt dit apparaat de 5-GHz-band wanneer deze beschikbaar is."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Hotspot-instellingen zijn gewijzigd"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Tik voor meer informatie"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-or/strings.xml b/packages/SettingsProvider/res/values-or/strings.xml
index 53f6104..4b73a55 100644
--- a/packages/SettingsProvider/res/values-or/strings.xml
+++ b/packages/SettingsProvider/res/values-or/strings.xml
@@ -20,7 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ସେଟିଙ୍ଗ ଷ୍ଟୋରେଜ୍‌"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"ଆପଣଙ୍କର ହଟ୍‌ସ୍ପଟ୍ ସେଟିଂସ୍‌କୁ ବଦଳାଇଥାଏ"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ଆପଣଙ୍କର ହଟ୍‌ସ୍ପଟ୍ ପରିବର୍ତ୍ତନ କରାଯାଇଛି।"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"କେବଳ 5GHz ପାଇଁ, ଏହି ଡିଭାଇସ୍ ଆପଣଙ୍କର ପସନ୍ଦକୁ ସମର୍ଥନ କରେ ନାହିଁ। ଏହା ପରିବର୍ତ୍ତେ, ଉପଲବ୍ଧ ହେଲେ ଏହି ଡିଭାଇସ୍ 5GHz ବ୍ୟାଣ୍ଡ ବ୍ୟବହାର କରିବ।"</string>
+    <!-- no translation found for wifi_softap_config_change (5688373762357941645) -->
+    <skip />
+    <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsProvider/res/values-pa/strings.xml b/packages/SettingsProvider/res/values-pa/strings.xml
index 9a41e36..5af8d6a 100644
--- a/packages/SettingsProvider/res/values-pa/strings.xml
+++ b/packages/SettingsProvider/res/values-pa/strings.xml
@@ -20,7 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ਸੈਟਿੰਗਾਂ ਸਟੋਰੇਜ"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"ਤੁਹਾਡੀਆਂ ਹੌਟਸਪੌਟ ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਬਦਲਾਅ"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ਤੁਹਾਡਾ ਹੌਟਸਪੌਟ ਬੈਂਡ ਬਦਲ ਗਿਆ ਹੈ।"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ਇਹ ਡੀਵਾਈਸ ਸਿਰਫ਼ 5GHz ਦੀ ਤੁਹਾਡੀ ਤਰਜੀਹ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦਾ ਹੈ। ਇਸਦੀ ਬਜਾਏ, ਇਹ ਡੀਵਾਈਸ ਉਪਲਬਧ ਹੋਣ \'ਤੇ 5GHz ਬੈਂਡ ਵਰਤੇਗਾ।"</string>
+    <!-- no translation found for wifi_softap_config_change (5688373762357941645) -->
+    <skip />
+    <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsProvider/res/values-pl/strings.xml b/packages/SettingsProvider/res/values-pl/strings.xml
index 7fd2b13..d86fc4d 100644
--- a/packages/SettingsProvider/res/values-pl/strings.xml
+++ b/packages/SettingsProvider/res/values-pl/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Pamięć ustawień"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Zmieniono ustawienia hotspotu"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Zmieniono pasmo hotspotu."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"To urządzenie nie może korzystać tylko z częstotliwości 5 GHz. Będzie korzystać z tego pasma, jeśli będzie ono dostępne."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Ustawienia hotspotu zostały zmienione"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Kliknij, by zobaczyć szczegóły"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-pt-rBR/strings.xml b/packages/SettingsProvider/res/values-pt-rBR/strings.xml
index 18db8f6..a860018 100644
--- a/packages/SettingsProvider/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsProvider/res/values-pt-rBR/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Armazenamento de configurações"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Mudanças nas suas configurações de ponto de acesso"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Sua banda de ponto de acesso foi modificada."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo não é compatível com sua preferência apenas por 5 GHz. Em vez disso, o dispositivo usará a banda de 5 GHz quando ela estiver disponível."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"As configurações de ponto de acesso mudaram"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Toque para ver os detalhes"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-pt-rPT/strings.xml b/packages/SettingsProvider/res/values-pt-rPT/strings.xml
index be88cce..a8a8e07 100644
--- a/packages/SettingsProvider/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsProvider/res/values-pt-rPT/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Armazenamento de definições"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Alterações às definições de zona Wi-Fi"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"A banda da sua zona Wi-Fi foi alterada."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo não suporta a sua preferência apenas para 5 GHz. Em alternativa, este dispositivo vai utilizar a banda de 5 GHz quando estiver disponível."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"As definições da zona Wi-Fi foram alteradas"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Toque para ver os detalhes."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-pt/strings.xml b/packages/SettingsProvider/res/values-pt/strings.xml
index 18db8f6..a860018 100644
--- a/packages/SettingsProvider/res/values-pt/strings.xml
+++ b/packages/SettingsProvider/res/values-pt/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Armazenamento de configurações"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Mudanças nas suas configurações de ponto de acesso"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Sua banda de ponto de acesso foi modificada."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Este dispositivo não é compatível com sua preferência apenas por 5 GHz. Em vez disso, o dispositivo usará a banda de 5 GHz quando ela estiver disponível."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"As configurações de ponto de acesso mudaram"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Toque para ver os detalhes"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ro/strings.xml b/packages/SettingsProvider/res/values-ro/strings.xml
index 3a234e1..561a213 100644
--- a/packages/SettingsProvider/res/values-ro/strings.xml
+++ b/packages/SettingsProvider/res/values-ro/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Stocare setări"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Modificări aduse setărilor pentru hotspot"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"S-a schimbat banda de frecvență a hotspotului."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Dispozitivul nu acceptă doar preferința pentru 5 GHz. Dispozitivul va folosi banda de 5 GHz când este disponibilă."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Setările hotspotului s-au modificat"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Atingeți pentru detalii"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ru/strings.xml b/packages/SettingsProvider/res/values-ru/strings.xml
index 184afdd..331fae1 100644
--- a/packages/SettingsProvider/res/values-ru/strings.xml
+++ b/packages/SettingsProvider/res/values-ru/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Хранилище настроек"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Изменения в настройках точки доступа"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Частота точки доступа изменена."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Устройство не может работать только на частоте 5 ГГц. Эта частота будет использоваться, когда это возможно."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Настройки точки доступа изменены"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Нажмите, чтобы узнать больше."</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-si/strings.xml b/packages/SettingsProvider/res/values-si/strings.xml
index 69e04f1..a9c4d0b 100644
--- a/packages/SettingsProvider/res/values-si/strings.xml
+++ b/packages/SettingsProvider/res/values-si/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"සැකසීම් ගබඩාව"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"ඔබගේ හොට්ස්පොට් සැකසීම්වලට වෙනස් කිරීම්"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ඔබගේ හොට්ස්පොට් කලාපය වෙනස් වී ඇත."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"මෙම උපාංගය 5GHz සඳහා ඔබේ මනාපවලට සහාය නොදක්වයි. ඒ වෙනුවට, මෙම උපාංගය ලබා ගත හැකි විට 5GHz කලාපය භාවිතා කරනු ඇත."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"හොට්ස්පොට් සැකසීම් වෙනස් කර ඇත"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"විස්තර බැලීමට තට්ටු කරන්න"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-sk/strings.xml b/packages/SettingsProvider/res/values-sk/strings.xml
index a53178d..5712d05 100644
--- a/packages/SettingsProvider/res/values-sk/strings.xml
+++ b/packages/SettingsProvider/res/values-sk/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Ukladací priestor nastavení"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Zmeny nastavení hotspotu"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Pásmo vášho hotspotu sa zmenilo."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Toto zariadenie nepodporuje vašu predvoľbu používať iba 5 GHz. Namiesto toho bude pásmo 5 GHz používať vtedy, keď bude k dispozícii."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Nastavenia hotspotu boli zmenené"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Klepnutím zobrazíte podrobnosti"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-sl/strings.xml b/packages/SettingsProvider/res/values-sl/strings.xml
index ea697fe..4e265fb 100644
--- a/packages/SettingsProvider/res/values-sl/strings.xml
+++ b/packages/SettingsProvider/res/values-sl/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Shramba nastavitev"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Spremembe nastavitev dostopne točke"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Pas dostopne točke je spremenjen."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Ta naprava ne podpira prednostne nastavitve samo za 5-GHz pas. Namesto tega bo ta naprava uporabljala 5-GHz pas, ko bo na voljo."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Nastavitve dostopne ročke so spremenjene"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Dotaknite se za ogled podrobnosti"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-sq/strings.xml b/packages/SettingsProvider/res/values-sq/strings.xml
index a111576..8bbe2e7 100644
--- a/packages/SettingsProvider/res/values-sq/strings.xml
+++ b/packages/SettingsProvider/res/values-sq/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Hapësira ruajtëse e \"Cilësimeve\""</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Ndryshimet në cilësimet e zonës së qasjes për internet"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Brezi yt i zonës së qasjes për internet ka ndryshuar."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Kjo pajisje nuk e mbështet preferencën për vetëm 5 GHz. Përkundrazi, pajisja do të përdorë brezin 5 GHz nëse ka."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Cilësimet e zonës së qasjes për internet kanë ndryshuar"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Trokit për të parë detajet"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-sr/strings.xml b/packages/SettingsProvider/res/values-sr/strings.xml
index d473102..4d05762 100644
--- a/packages/SettingsProvider/res/values-sr/strings.xml
+++ b/packages/SettingsProvider/res/values-sr/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Подешавања складишта"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Промене подешавања за хотспот"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Опсег хотспота је промењен."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Овај уређај не подржава подешавање само за 5 GHz. Уређај ће користити опсег од 5 GHz када буде доступан."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Подешавања хотспота су промењена"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Додирните да бисте видели детаље"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-sv/strings.xml b/packages/SettingsProvider/res/values-sv/strings.xml
index fea3e5e..5ee4703 100644
--- a/packages/SettingsProvider/res/values-sv/strings.xml
+++ b/packages/SettingsProvider/res/values-sv/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Lagring av inställningar"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Ändringar i inställningarna för surfzon"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Frekvensbandet för surfzonen har ändrats."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Den här enheten har inte stöd för inställningen för att endast använda 5 GHz. I stället används 5 GHz när det är möjligt."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Inställningarna för surfzon har ändrats"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Tryck här för mer information"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-sw/strings.xml b/packages/SettingsProvider/res/values-sw/strings.xml
index 4d05817..59f82a9 100644
--- a/packages/SettingsProvider/res/values-sw/strings.xml
+++ b/packages/SettingsProvider/res/values-sw/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Hifadhi ya Mipangilio"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Mabadiliko kwenye mipangilio ya mtandaopepe"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Bendi ya mtandaopepe wako imebadilika."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Kifaa hiki hakitumii mapendeleo yako ya GHz 5 pekee. Badala yake, kifaa hiki kitatumia bendi ya GHz 5 itakapopatikana."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Mipangilio ya mtandaopepe imebadilika"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Gusa ili uangalie maelezo"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ta/strings.xml b/packages/SettingsProvider/res/values-ta/strings.xml
index f518a78..fa6b8cd 100644
--- a/packages/SettingsProvider/res/values-ta/strings.xml
+++ b/packages/SettingsProvider/res/values-ta/strings.xml
@@ -20,7 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"அமைப்புகளின் சேமிப்பிடம்"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"உங்கள் ஹாட்ஸ்பாட் அமைப்புகளில் செய்யப்பட்டுள்ள மாற்றங்கள்"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"உங்கள் ஹாட்ஸ்பாட்டின் அலைவரிசை வரம்பு மாறிவிட்டது."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"இந்தச் சாதனத்தில், ’5GHz மட்டும்’ எனும் முன்னுரிமைத் தேர்வு ஆதரிக்கப்படவில்லை. எனினும் 5GHz அலைவரிசை வரம்பிற்குள் இருக்கும்போது சாதனம் அதைப் பயன்படுத்திக்கொள்ளும்."</string>
+    <!-- no translation found for wifi_softap_config_change (5688373762357941645) -->
+    <skip />
+    <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsProvider/res/values-te/strings.xml b/packages/SettingsProvider/res/values-te/strings.xml
index 6c59223..b1955ed 100644
--- a/packages/SettingsProvider/res/values-te/strings.xml
+++ b/packages/SettingsProvider/res/values-te/strings.xml
@@ -20,7 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"సెట్టింగ్‌ల నిల్వ"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"మీ హాట్‌స్పాట్ సెట్టింగ్‌లకు మార్పులు"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"మీ హాట్‌స్పాట్ బ్యాండ్ మార్చబడింది."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"ఈ పరికరం 5GHz కోసం మాత్రమే మీ ప్రాధాన్యతకు మద్దతు ఇవ్వదు. బదులుగా, ఈ పరికరం అందుబాటులో ఉన్నప్పుడు 5GHz బ్యాండ్‌ను ఉపయోగిస్తుంది."</string>
+    <!-- no translation found for wifi_softap_config_change (5688373762357941645) -->
+    <skip />
+    <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsProvider/res/values-th/strings.xml b/packages/SettingsProvider/res/values-th/strings.xml
index 4bf148f..ed63174 100644
--- a/packages/SettingsProvider/res/values-th/strings.xml
+++ b/packages/SettingsProvider/res/values-th/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ที่เก็บข้อมูลการตั้งค่า"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"มีการเปลี่ยนแปลงการตั้งค่าฮอตสปอต"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"ย่านความถี่ฮอตสปอตมีการเปลี่ยนแปลง"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"อุปกรณ์นี้ไม่รองรับค่ากำหนดของคุณเฉพาะสำหรับ 5 GHz เท่านั้น และจะใช้ย่านความถี่ 5 GHz แทน เมื่อใช้ได้"</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"การตั้งค่าฮอตสปอตมีการเปลี่ยนแปลง"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"แตะเพื่อดูรายละเอียด"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-tl/strings.xml b/packages/SettingsProvider/res/values-tl/strings.xml
index 2a36d58..3d6be40 100644
--- a/packages/SettingsProvider/res/values-tl/strings.xml
+++ b/packages/SettingsProvider/res/values-tl/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Storage ng Mga Setting"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Mga pagbabago sa mga setting ng iyong hotspot"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Nagbago ang band ng iyong hotspot."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Hindi sinusuportahan ng device na ito ang kagustuhan mong gumamit lang ng 5GHz. Sa halip, gagamitin ng device na ito ang 5GHz na band kapag available."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Nabago ang mga setting ng hotspot"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"I-tap para makita ang mga detalye"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-tr/strings.xml b/packages/SettingsProvider/res/values-tr/strings.xml
index add1fdb..75e908f 100644
--- a/packages/SettingsProvider/res/values-tr/strings.xml
+++ b/packages/SettingsProvider/res/values-tr/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Ayarlar Deposu"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Hotspot ayarlarınız değişti"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Hotspot bandınız değişti."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Bu cihaz yalnızca 5 GHz bandının kullanılmasına yönelik tercihinizi desteklemiyor. Bunun yerine, bu cihaz 5 GHz bandını mevcut olduğunda kullanacak."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Hotspot ayarları değişti"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Ayrıntıları görmek için dokunun"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-uk/strings.xml b/packages/SettingsProvider/res/values-uk/strings.xml
index cd678bc..2dbb360 100644
--- a/packages/SettingsProvider/res/values-uk/strings.xml
+++ b/packages/SettingsProvider/res/values-uk/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Сховище налаштувань"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Зміни в налаштуваннях точки доступу"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Діапазон частот точки доступу змінено."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"На цьому пристрої не підтримується налаштування \"Лише 5 ГГц\". Натомість буде використано діапазон частот 5 ГГц (якщо доступно)."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Налаштування точки доступу змінились"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Торкніться, щоб переглянути докладні відомості"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-ur/strings.xml b/packages/SettingsProvider/res/values-ur/strings.xml
index 2241ce9..2ce44b1 100644
--- a/packages/SettingsProvider/res/values-ur/strings.xml
+++ b/packages/SettingsProvider/res/values-ur/strings.xml
@@ -20,7 +20,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"ترتیبات کا اسٹوریج"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"اپنے ہاٹ اسپاٹ کی ترتیبات میں تبدیلیاں"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"آپ کا ہاٹ اسپات بینڈ تبدیل ہو گیا ہے۔"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"‏یہ آلہ صرف 5GHz کے لیے آپ کی ترجیح کو سپورٹ نہیں کرے گا۔ بلکہ 5GHz بینڈ کے دستیاب ہونے پر اس کا استعمال کرے گا۔"</string>
+    <!-- no translation found for wifi_softap_config_change (5688373762357941645) -->
+    <skip />
+    <!-- no translation found for wifi_softap_config_change_summary (8946397286141531087) -->
+    <skip />
 </resources>
diff --git a/packages/SettingsProvider/res/values-uz/strings.xml b/packages/SettingsProvider/res/values-uz/strings.xml
index a266bf0..bb6e22e 100644
--- a/packages/SettingsProvider/res/values-uz/strings.xml
+++ b/packages/SettingsProvider/res/values-uz/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Sozlamalar xotirasi"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Hotspot sozlamalari o‘zgartirildi"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Hotspot chastotasi oʻzgartirildi."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Qurilma faqat 5 GGs chastotada ishlay olmaydi. Bu chastotadan imkoniyatga qarab foydalaniladi."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Hotspot sozlamalari oʻzgardi"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Tafsilotlar uchun bosing"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-vi/strings.xml b/packages/SettingsProvider/res/values-vi/strings.xml
index 74f93b2..4608983 100644
--- a/packages/SettingsProvider/res/values-vi/strings.xml
+++ b/packages/SettingsProvider/res/values-vi/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Lưu trữ bộ nhớ"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Những thay đổi trong mục cài đặt điểm phát sóng của bạn"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Bằng tần của điểm phát sóng đã thay đổi."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Thiết bị này không hỗ trợ tùy chọn chỉ sử dụng băng tần 5 GHz. Thay vào đó, thiết bị này sẽ sử dụng băng tần 5 GHz khi có thể."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Đã thay đổi các tùy chọn cài đặt điểm phát sóng"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Nhấn để xem chi tiết"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-zh-rCN/strings.xml b/packages/SettingsProvider/res/values-zh-rCN/strings.xml
index 95b15e0..a08afc8 100644
--- a/packages/SettingsProvider/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsProvider/res/values-zh-rCN/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"设置存储"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"您的热点设置已变更"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"您的热点频段已变更。"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"此设备不支持您的偏好设置(仅限 5GHz),而且会在 5GHz 频段可用时使用该频段。"</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"热点设置已更改"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"点按即可查看详细信息"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-zh-rHK/strings.xml b/packages/SettingsProvider/res/values-zh-rHK/strings.xml
index 41ebe27..fb91dbb 100644
--- a/packages/SettingsProvider/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsProvider/res/values-zh-rHK/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"設定儲存空間"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"您的熱點設定變更"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"您的熱點頻段已變更。"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"此裝置不支援只限 5 GHz 的偏好設定,但會在 5 GHz 頻段可用時採用。"</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"熱點設定已變更"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"輕按以查看詳情"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-zh-rTW/strings.xml b/packages/SettingsProvider/res/values-zh-rTW/strings.xml
index d0a30f5..1b8fcb2 100644
--- a/packages/SettingsProvider/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsProvider/res/values-zh-rTW/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"設定儲存空間"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"無線基地台設定變更"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"你的無線基地台頻帶已變更。"</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"這部裝置不支援你的偏好設定 (僅限 5GHz),而是會在 5GHz 可用時使用該頻帶。"</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"無線基地台設定有所變更"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"輕觸即可查看詳細資料"</string>
 </resources>
diff --git a/packages/SettingsProvider/res/values-zu/strings.xml b/packages/SettingsProvider/res/values-zu/strings.xml
index 0440b3b..dad24b4 100644
--- a/packages/SettingsProvider/res/values-zu/strings.xml
+++ b/packages/SettingsProvider/res/values-zu/strings.xml
@@ -20,7 +20,6 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="4567566098528588863">"Izilungiselelo zesitoreji"</string>
-    <string name="wifi_softap_config_change" msgid="5338670993556993667">"Ushintsho kuzilungiselelo zakho ze-hotspot"</string>
-    <string name="wifi_softap_config_change_summary" msgid="7600005249167787750">"Ibhendi yakho ye-hotspot ishintshile."</string>
-    <string name="wifi_softap_config_change_detailed" msgid="2504664754843959730">"Le divayisi ayisekeli okuncamelayo kwe-5GHz kuphela. Kunalokho, le divayisi izosebenzisa ibhendi ye-5GHz uma itholakala."</string>
+    <string name="wifi_softap_config_change" msgid="5688373762357941645">"Izilungiselelo ze-Hotspot zishintshile"</string>
+    <string name="wifi_softap_config_change_summary" msgid="8946397286141531087">"Thepha ukuze ubone imininingwane"</string>
 </resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
index e80b437..7dcf4b0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SyncRtSurfaceTransactionApplierCompat.java
@@ -24,6 +24,7 @@
 import android.os.Message;
 import android.os.Trace;
 import android.view.Surface;
+import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewRootImpl;
 
@@ -39,7 +40,7 @@
 
     private static final int MSG_UPDATE_SEQUENCE_NUMBER = 0;
 
-    private final Surface mTargetSurface;
+    private final SurfaceControl mBarrierSurfaceControl;
     private final ViewRootImpl mTargetViewRootImpl;
     private final Handler mApplyHandler;
 
@@ -52,7 +53,8 @@
      */
     public SyncRtSurfaceTransactionApplierCompat(View targetView) {
         mTargetViewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
-        mTargetSurface = mTargetViewRootImpl != null ? mTargetViewRootImpl.mSurface : null;
+        mBarrierSurfaceControl = mTargetViewRootImpl != null
+            ? mTargetViewRootImpl.getRenderSurfaceControl() : null;
 
         mApplyHandler = new Handler(new Callback() {
             @Override
@@ -91,7 +93,7 @@
         mTargetViewRootImpl.registerRtFrameCallback(new HardwareRenderer.FrameDrawingCallback() {
             @Override
             public void onFrameDraw(long frame) {
-                if (mTargetSurface == null || !mTargetSurface.isValid()) {
+                if (mBarrierSurfaceControl == null || !mBarrierSurfaceControl.isValid()) {
                     Message.obtain(mApplyHandler, MSG_UPDATE_SEQUENCE_NUMBER, toApplySeqNo, 0)
                             .sendToTarget();
                     return;
@@ -102,7 +104,7 @@
                     SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams =
                             params[i];
                     SurfaceControlCompat surface = surfaceParams.surface;
-                    t.deferTransactionUntil(surface, mTargetSurface, frame);
+                    t.deferTransactionUntil(surface, mBarrierSurfaceControl, frame);
                     applyParams(t, surfaceParams);
                 }
                 t.setEarlyWakeup();
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
index 073688b..8f4926f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TransactionCompat.java
@@ -20,6 +20,7 @@
 import android.graphics.Rect;
 import android.view.Surface;
 import android.view.SurfaceControl.Transaction;
+import android.view.SurfaceControl;
 
 public class TransactionCompat {
 
@@ -87,8 +88,8 @@
     }
 
     public TransactionCompat deferTransactionUntil(SurfaceControlCompat surfaceControl,
-            Surface barrier, long frameNumber) {
-        mTransaction.deferTransactionUntilSurface(surfaceControl.mSurfaceControl, barrier,
+            SurfaceControl barrier, long frameNumber) {
+        mTransaction.deferTransactionUntil(surfaceControl.mSurfaceControl, barrier,
                 frameNumber);
         return this;
     }
@@ -102,4 +103,4 @@
         mTransaction.setColor(surfaceControl.mSurfaceControl, color);
         return this;
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index e5f6d3c..9ba3860 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -95,6 +95,7 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.util.Assert;
 
 import com.google.android.collect.Lists;
 
@@ -341,7 +342,7 @@
 
     @Override
     public void onTrustChanged(boolean enabled, int userId, int flags) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         mUserHasTrust.put(userId, enabled);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -360,7 +361,7 @@
     }
 
     private void handleSimSubscriptionInfoChanged() {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (DEBUG_SIM_STATES) {
             Log.v(TAG, "onSubscriptionInfoChanged()");
             List<SubscriptionInfo> sil = mSubscriptionManager
@@ -403,7 +404,7 @@
     }
 
     private void callbacksRefreshCarrierInfo() {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -466,7 +467,7 @@
 
     @Override
     public void onTrustManagedChanged(boolean managed, int userId) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         mUserTrustIsManaged.put(userId, managed);
         mUserTrustIsUsuallyManaged.put(userId, mTrustManager.isTrustUsuallyManaged(userId));
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -523,7 +524,7 @@
 
     @VisibleForTesting
     protected void onFingerprintAuthenticated(int userId) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         Trace.beginSection("KeyGuardUpdateMonitor#onFingerPrintAuthenticated");
         mUserFingerprintAuthenticated.put(userId, true);
         // Update/refresh trust state only if user can skip bouncer
@@ -549,7 +550,7 @@
     }
 
     private void handleFingerprintAuthFailed() {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -561,7 +562,7 @@
     }
 
     private void handleFingerprintAcquired(int acquireInfo) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (acquireInfo != FingerprintManager.FINGERPRINT_ACQUIRED_GOOD) {
             return;
         }
@@ -599,7 +600,7 @@
     }
 
     private void handleFingerprintHelp(int msgId, String helpString) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -618,7 +619,7 @@
     };
 
     private void handleFingerprintError(int msgId, String errString) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED && mHandler.hasCallbacks(
                 mCancelNotReceived)) {
             mHandler.removeCallbacks(mCancelNotReceived);
@@ -671,7 +672,7 @@
     }
 
     private void notifyFingerprintRunningStateChanged() {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -684,7 +685,7 @@
     @VisibleForTesting
     protected void onFaceAuthenticated(int userId) {
         Trace.beginSection("KeyGuardUpdateMonitor#onFaceAuthenticated");
-        checkIsHandlerThread();
+        Assert.isMainThread();
         mUserFaceAuthenticated.put(userId, true);
         // Update/refresh trust state only if user can skip bouncer
         if (getUserCanSkipBouncer(userId)) {
@@ -710,7 +711,7 @@
     }
 
     private void handleFaceAuthFailed() {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         setFaceRunningState(BIOMETRIC_STATE_STOPPED);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -723,7 +724,7 @@
     }
 
     private void handleFaceAcquired(int acquireInfo) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (acquireInfo != FaceManager.FACE_ACQUIRED_GOOD) {
             return;
         }
@@ -767,7 +768,7 @@
     }
 
     private void handleFaceHelp(int msgId, String helpString) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (DEBUG_FACE) Log.d(TAG, "Face help received: " + helpString);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -787,7 +788,7 @@
     };
 
     private void handleFaceError(int msgId, String errString) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (DEBUG_FACE) Log.d(TAG, "Face error received: " + errString);
         if (msgId == FaceManager.FACE_ERROR_CANCELED && mHandler.hasCallbacks(mCancelNotReceived)) {
             mHandler.removeCallbacks(mCancelNotReceived);
@@ -842,7 +843,7 @@
     }
 
     private void notifyFaceRunningStateChanged() {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -853,7 +854,7 @@
     }
 
     private void handleFaceUnlockStateChanged(boolean running, int userId) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         mUserFaceUnlockRunning.put(userId, running);
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -965,7 +966,7 @@
      * Cached version of {@link TrustManager#isTrustUsuallyManaged(int)}.
      */
     public boolean isTrustUsuallyManaged(int userId) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         return mUserTrustIsUsuallyManaged.get(userId);
     }
 
@@ -996,7 +997,7 @@
     }
 
     private void notifyStrongAuthStateChanged(int userId) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -1010,7 +1011,7 @@
     }
 
     private void dispatchErrorMessage(CharSequence message) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -1323,7 +1324,7 @@
 
     protected void handleStartedWakingUp() {
         Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
-        checkIsHandlerThread();
+        Assert.isMainThread();
         updateBiometricListeningState();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1335,7 +1336,7 @@
     }
 
     protected void handleStartedGoingToSleep(int arg1) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         mLockIconPressed = false;
         clearBiometricRecognized();
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1349,7 +1350,7 @@
     }
 
     protected void handleFinishedGoingToSleep(int arg1) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         mGoingToSleep = false;
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1361,7 +1362,7 @@
     }
 
     private void handleScreenTurnedOn() {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -1373,7 +1374,7 @@
     private void handleScreenTurnedOff() {
         final String tag = "KeyguardUpdateMonitor#handleScreenTurnedOff";
         DejankUtils.startDetectingBlockingIpcs(tag);
-        checkIsHandlerThread();
+        Assert.isMainThread();
         mHardwareFingerprintUnavailableRetryCount = 0;
         mHardwareFaceUnavailableRetryCount = 0;
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1386,7 +1387,7 @@
     }
 
     private void handleDreamingStateChanged(int dreamStart) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         mIsDreaming = dreamStart == 1;
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1398,7 +1399,7 @@
     }
 
     private void handleUserInfoChanged(int userId) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -1408,7 +1409,7 @@
     }
 
     private void handleUserUnlocked(int userId) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         mUserIsUnlocked.put(userId, true);
         mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1420,20 +1421,22 @@
     }
 
     private void handleUserStopped(int userId) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         mUserIsUnlocked.put(userId, mUserManager.isUserUnlocked(userId));
     }
 
     @VisibleForTesting
     void handleUserRemoved(int userId) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         mUserIsUnlocked.delete(userId);
         mUserTrustIsUsuallyManaged.delete(userId);
     }
 
     @VisibleForTesting
     @Inject
-    protected KeyguardUpdateMonitor(Context context, @Main Looper mainLooper,
+    protected KeyguardUpdateMonitor(
+            Context context,
+            @Main Looper mainLooper,
             BroadcastDispatcher broadcastDispatcher,
             DumpController dumpController) {
         mContext = context;
@@ -1962,7 +1965,7 @@
      * @param hasLockscreenWallpaper Whether Keyguard has a lockscreen wallpaper.
      */
     public void setHasLockscreenWallpaper(boolean hasLockscreenWallpaper) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (hasLockscreenWallpaper != mHasLockscreenWallpaper) {
             mHasLockscreenWallpaper = hasLockscreenWallpaper;
             for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1985,7 +1988,7 @@
      * Handle {@link #MSG_DPM_STATE_CHANGED}
      */
     private void handleDevicePolicyManagerStateChanged(int userId) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         updateFingerprintListeningState();
         updateSecondaryLockscreenRequirement(userId);
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -2000,7 +2003,7 @@
      * Handle {@link #MSG_USER_SWITCHING}
      */
     private void handleUserSwitching(int userId, IRemoteCallback reply) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         mUserTrustIsUsuallyManaged.put(userId, mTrustManager.isTrustUsuallyManaged(userId));
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -2018,7 +2021,7 @@
      * Handle {@link #MSG_USER_SWITCH_COMPLETE}
      */
     private void handleUserSwitchComplete(int userId) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -2031,7 +2034,7 @@
      * Handle {@link #MSG_DEVICE_PROVISIONED}
      */
     private void handleDeviceProvisioned() {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -2049,7 +2052,7 @@
      * Handle {@link #MSG_PHONE_STATE_CHANGED}
      */
     private void handlePhoneStateChanged(String newState) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (DEBUG) Log.d(TAG, "handlePhoneStateChanged(" + newState + ")");
         if (TelephonyManager.EXTRA_STATE_IDLE.equals(newState)) {
             mPhoneState = TelephonyManager.CALL_STATE_IDLE;
@@ -2070,7 +2073,7 @@
      * Handle {@link #MSG_RINGER_MODE_CHANGED}
      */
     private void handleRingerModeChange(int mode) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (DEBUG) Log.d(TAG, "handleRingerModeChange(" + mode + ")");
         mRingMode = mode;
         for (int i = 0; i < mCallbacks.size(); i++) {
@@ -2085,7 +2088,7 @@
      * Handle {@link #MSG_TIME_UPDATE}
      */
     private void handleTimeUpdate() {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (DEBUG) Log.d(TAG, "handleTimeUpdate");
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -2099,7 +2102,7 @@
      * Handle (@line #MSG_TIMEZONE_UPDATE}
      */
     private void handleTimeZoneUpdate(String timeZone) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (DEBUG) Log.d(TAG, "handleTimeZoneUpdate");
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -2115,7 +2118,7 @@
      * Handle {@link #MSG_BATTERY_UPDATE}
      */
     private void handleBatteryUpdate(BatteryStatus status) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (DEBUG) Log.d(TAG, "handleBatteryUpdate");
         final boolean batteryUpdateInteresting = isBatteryUpdateInteresting(mBatteryStatus, status);
         mBatteryStatus = status;
@@ -2134,7 +2137,7 @@
      */
     @VisibleForTesting
     void updateTelephonyCapable(boolean capable) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (capable == mTelephonyCapable) {
             return;
         }
@@ -2152,7 +2155,7 @@
      */
     @VisibleForTesting
     void handleSimStateChange(int subId, int slotId, int state) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (DEBUG_SIM_STATES) {
             Log.d(TAG, "handleSimStateChange(subId=" + subId + ", slotId="
                     + slotId + ", state=" + state + ")");
@@ -2236,7 +2239,7 @@
      * <p>Needs to be called from the main thread.
      */
     public void onKeyguardVisibilityChanged(boolean showing) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         Log.d(TAG, "onKeyguardVisibilityChanged(" + showing + ")");
         mKeyguardIsVisible = showing;
 
@@ -2279,7 +2282,7 @@
      * @see #sendKeyguardBouncerChanged(boolean)
      */
     private void handleKeyguardBouncerChanged(int bouncer) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (DEBUG) Log.d(TAG, "handleKeyguardBouncerChanged(" + bouncer + ")");
         boolean isBouncer = (bouncer == 1);
         mBouncer = isBouncer;
@@ -2305,7 +2308,7 @@
      * Handle {@link #MSG_REPORT_EMERGENCY_CALL_ACTION}
      */
     private void handleReportEmergencyCallAction() {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         for (int i = 0; i < mCallbacks.size(); i++) {
             KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
             if (cb != null) {
@@ -2344,7 +2347,7 @@
      * @param callback The callback to remove
      */
     public void removeCallback(KeyguardUpdateMonitorCallback callback) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (DEBUG) {
             Log.v(TAG, "*** unregister callback for " + callback);
         }
@@ -2359,7 +2362,7 @@
      * @param callback The callback to register
      */
     public void registerCallback(KeyguardUpdateMonitorCallback callback) {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         if (DEBUG) Log.v(TAG, "*** register callback for " + callback);
         // Prevent adding duplicate callbacks
 
@@ -2448,7 +2451,7 @@
         if (!bypassHandler) {
             mHandler.obtainMessage(MSG_REPORT_EMERGENCY_CALL_ACTION).sendToTarget();
         } else {
-            checkIsHandlerThread();
+            Assert.isMainThread();
             handleReportEmergencyCallAction();
         }
     }
@@ -2466,7 +2469,7 @@
     }
 
     public void clearBiometricRecognized() {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         mUserFingerprintAuthenticated.clear();
         mUserFaceAuthenticated.clear();
         mTrustManager.clearAllBiometricRecognized(BiometricSourceType.FINGERPRINT);
@@ -2653,7 +2656,7 @@
     }
 
     private void updateLogoutEnabled() {
-        checkIsHandlerThread();
+        Assert.isMainThread();
         boolean logoutEnabled = mDevicePolicyManager.isLogoutEnabled();
         if (mLogoutEnabled != logoutEnabled) {
             mLogoutEnabled = logoutEnabled;
@@ -2667,13 +2670,6 @@
         }
     }
 
-    private void checkIsHandlerThread() {
-        if (!mHandler.getLooper().isCurrentThread()) {
-            Log.wtfStack(TAG, "must call on mHandler's thread "
-                    + mHandler.getLooper().getThread() + ", not " + Thread.currentThread());
-        }
-    }
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("KeyguardUpdateMonitor state:");
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 3d9865c..955edcf 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -648,7 +648,7 @@
         String appName = topBubble.getAppName();
         Notification notification = topBubble.getEntry().getSbn().getNotification();
         CharSequence titleCharSeq = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
-        String titleStr = getResources().getString(R.string.stream_notification);
+        String titleStr = getResources().getString(R.string.notification_bubble_title);
         if (titleCharSeq != null) {
             titleStr = titleCharSeq.toString();
         }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index a57ec5b..3e257b6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -38,8 +38,10 @@
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
 import com.android.systemui.statusbar.phone.ShadeControllerImpl;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -92,10 +94,14 @@
 
     @Singleton
     @Provides
-    static HeadsUpManagerPhone provideHeadsUpManagerPhone(Context context,
+    static HeadsUpManagerPhone provideHeadsUpManagerPhone(
+            Context context,
             StatusBarStateController statusBarStateController,
-            KeyguardBypassController bypassController) {
-        return new HeadsUpManagerPhone(context, statusBarStateController, bypassController);
+            KeyguardBypassController bypassController,
+            NotificationGroupManager groupManager,
+            ConfigurationController configurationController) {
+        return new HeadsUpManagerPhone(context, statusBarStateController, bypassController,
+                groupManager, configurationController);
     }
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 0c86157..1ab77f3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -17,9 +17,9 @@
 package com.android.systemui.qs.tiles;
 
 import android.annotation.Nullable;
-import android.content.ComponentName;
 import android.content.Intent;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.service.quicksettings.Tile;
 import android.util.Log;
 import android.widget.Switch;
@@ -36,9 +36,6 @@
 
 /** Quick settings tile: Hotspot **/
 public class HotspotTile extends QSTileImpl<BooleanState> {
-    private static final Intent TETHER_SETTINGS = new Intent().setComponent(new ComponentName(
-            "com.android.settings", "com.android.settings.TetherSettings"));
-
     private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot);
 
     private final HotspotController mHotspotController;
@@ -79,7 +76,7 @@
 
     @Override
     public Intent getLongClickIntent() {
-        return new Intent(TETHER_SETTINGS);
+        return new Intent(Settings.ACTION_TETHER_SETTINGS);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
index 72a7e11..07cf9d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
@@ -53,12 +53,14 @@
             VisualStabilityManager visualStabilityManager,
             StatusBarStateController statusBarStateController,
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
-            NotificationListener notificationListener) {
+            NotificationListener notificationListener,
+            HeadsUpManager headsUpManager) {
         mRemoteInputManager = remoteInputManager;
         mVisualStabilityManager = visualStabilityManager;
         mStatusBarStateController = statusBarStateController;
         mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
         mNotificationListener = notificationListener;
+        mHeadsUpManager = headsUpManager;
 
         notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
             @Override
@@ -81,10 +83,6 @@
         });
     }
 
-    public void setHeadsUpManager(HeadsUpManager headsUpManager) {
-        mHeadsUpManager = headsUpManager;
-    }
-
     /**
      * Adds the entry to the respective alerting manager if the content view was inflated and
      * the entry should still alert.
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 4f55e02..5ebd368 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -49,9 +49,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.Assert;
 import com.android.systemui.util.leak.LeakDetector;
 
@@ -230,9 +228,7 @@
         mRemoveInterceptors.remove(interceptor);
     }
 
-    public void setUpWithPresenter(NotificationPresenter presenter,
-            NotificationListContainer listContainer,
-            HeadsUpManager headsUpManager) {
+    public void setUpWithPresenter(NotificationPresenter presenter) {
         mPresenter = presenter;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index 616c110..fabe3a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -61,7 +61,8 @@
      * Injected constructor. See {@link NotificationsModule}.
      */
     public VisualStabilityManager(
-            NotificationEntryManager notificationEntryManager, @Main Handler handler) {
+            NotificationEntryManager notificationEntryManager,
+            @Main Handler handler) {
 
         mHandler = handler;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 8f8f742..d0b553d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.util.leak.LeakDetector;
 
 import java.util.concurrent.Executor;
@@ -123,14 +124,16 @@
             VisualStabilityManager visualStabilityManager,
             StatusBarStateController statusBarStateController,
             NotificationInterruptionStateProvider notificationInterruptionStateProvider,
-            NotificationListener notificationListener) {
+            NotificationListener notificationListener,
+            HeadsUpManager headsUpManager) {
         return new NotificationAlertingManager(
                 notificationEntryManager,
                 remoteInputManager,
                 visualStabilityManager,
                 statusBarStateController,
                 notificationInterruptionStateProvider,
-                notificationListener);
+                notificationListener,
+                headsUpManager);
     }
 
     /** Provides an instance of {@link NotificationLogger} */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 3eac229..b03ba3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -336,6 +336,10 @@
         return false;
     }
 
+    boolean superPerformClick() {
+        return super.performClick();
+    }
+
     /**
      * Cancels the hotspot and makes the notification inactive.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
index 8465658..2643ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
@@ -72,7 +72,8 @@
             } else {
                 mView.makeInactive(true /* animate */);
             }
-        }, mView::performClick, mView::handleSlideBack, mFalsingManager::onNotificationDoubleTap);
+        }, mView::superPerformClick, mView::handleSlideBack,
+                mFalsingManager::onNotificationDoubleTap);
         mView.setOnTouchListener(mTouchHandler);
         mView.setTouchHandler(mTouchHandler);
         mView.setOnDimmedListener(dimmed -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index c6e3fde..63fe700 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -19,24 +19,15 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.content.res.Resources;
-import android.graphics.Rect;
 import android.graphics.Region;
-import android.util.Log;
 import android.util.Pools;
-import android.view.DisplayCutout;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
 
 import androidx.collection.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
-import com.android.systemui.ScreenDecorations;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.StatusBarState;
@@ -49,31 +40,27 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Stack;
 
 /**
  * A implementation of HeadsUpManager for phone and car.
  */
 public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
-        VisualStabilityManager.Callback, OnHeadsUpChangedListener,
-        ConfigurationController.ConfigurationListener, StateListener {
+        VisualStabilityManager.Callback, OnHeadsUpChangedListener {
     private static final String TAG = "HeadsUpManagerPhone";
 
     @VisibleForTesting
     final int mExtensionTime;
-    private final StatusBarStateController mStatusBarStateController;
     private final KeyguardBypassController mBypassController;
+    private final NotificationGroupManager mGroupManager;
+    private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>();
     private final int mAutoHeadsUpNotificationDecay;
-    private View mNotificationShadeWindowView;
-    private NotificationGroupManager mGroupManager;
     private VisualStabilityManager mVisualStabilityManager;
-    private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
     private boolean mReleaseOnExpandFinish;
 
-    private int mStatusBarHeight;
-    private int mHeadsUpInset;
-    private int mDisplayCutoutTouchableRegionSize;
     private boolean mTrackingHeadsUp;
     private HashSet<String> mSwipedOutKeys = new HashSet<>();
     private HashSet<NotificationEntry> mEntriesToRemoveAfterExpand = new HashSet<>();
@@ -81,12 +68,13 @@
     private ArraySet<NotificationEntry> mEntriesToRemoveWhenReorderingAllowed
             = new ArraySet<>();
     private boolean mIsExpanded;
-    private int[] mTmpTwoArray = new int[2];
     private boolean mHeadsUpGoingAway;
     private int mStatusBarState;
-    private Region mTouchableRegion = new Region();
-
     private AnimationStateHandler mAnimationStateHandler;
+    private int mHeadsUpInset;
+
+    // Used for determining the region for touch interaction
+    private final Region mTouchableRegion = new Region();
 
     private final Pools.Pool<HeadsUpEntryPhone> mEntryPool = new Pools.Pool<HeadsUpEntryPhone>() {
         private Stack<HeadsUpEntryPhone> mPoolObjects = new Stack<>();
@@ -111,76 +99,96 @@
 
     public HeadsUpManagerPhone(@NonNull final Context context,
             StatusBarStateController statusBarStateController,
-            KeyguardBypassController bypassController) {
+            KeyguardBypassController bypassController,
+            NotificationGroupManager groupManager,
+            ConfigurationController configurationController) {
         super(context);
         Resources resources = mContext.getResources();
         mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time);
         mAutoHeadsUpNotificationDecay = resources.getInteger(
                 R.integer.auto_heads_up_notification_decay);
-        mStatusBarStateController = statusBarStateController;
-        mStatusBarStateController.addCallback(this);
+        statusBarStateController.addCallback(mStatusBarStateListener);
         mBypassController = bypassController;
-
-        initResources();
-    }
-
-    public void setUp(@NonNull View notificationShadeWindowView,
-            @NonNull NotificationGroupManager groupManager,
-            @NonNull StatusBar bar,
-            @NonNull VisualStabilityManager visualStabilityManager) {
-        mNotificationShadeWindowView = notificationShadeWindowView;
-        mStatusBarTouchableRegionManager = new StatusBarTouchableRegionManager(this, bar,
-                notificationShadeWindowView);
         mGroupManager = groupManager;
-        mVisualStabilityManager = visualStabilityManager;
 
-        addListener(new OnHeadsUpChangedListener() {
+        updateResources();
+        configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
             @Override
-            public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) {
-                if (Log.isLoggable(TAG, Log.WARN)) {
-                    Log.w(TAG, "onHeadsUpPinnedModeChanged");
-                }
-                mStatusBarTouchableRegionManager.updateTouchableRegion();
+            public void onDensityOrFontScaleChanged() {
+                updateResources();
+            }
+
+            @Override
+            public void onOverlayChanged() {
+                updateResources();
             }
         });
     }
 
+    void setup(VisualStabilityManager visualStabilityManager) {
+        mVisualStabilityManager = visualStabilityManager;
+    }
+
     public void setAnimationStateHandler(AnimationStateHandler handler) {
         mAnimationStateHandler = handler;
     }
 
-    private void initResources() {
+    private void updateResources() {
         Resources resources = mContext.getResources();
-        mStatusBarHeight = resources.getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_height);
-        mHeadsUpInset = mStatusBarHeight + resources.getDimensionPixelSize(
-                R.dimen.heads_up_status_bar_padding);
-        mDisplayCutoutTouchableRegionSize = resources.getDimensionPixelSize(
-                com.android.internal.R.dimen.display_cutout_touchable_region_size);
-    }
-
-    @Override
-    public void onDensityOrFontScaleChanged() {
-        super.onDensityOrFontScaleChanged();
-        initResources();
-    }
-
-    @Override
-    public void onOverlayChanged() {
-        initResources();
+        mHeadsUpInset =
+                resources.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height)
+                        + resources.getDimensionPixelSize(R.dimen.heads_up_status_bar_padding);
     }
 
     ///////////////////////////////////////////////////////////////////////////////////////////////
     //  Public methods:
 
     /**
+     * Add a listener to receive callbacks onHeadsUpGoingAway
+     */
+    void addHeadsUpPhoneListener(OnHeadsUpPhoneListenerChange listener) {
+        mHeadsUpPhoneListeners.add(listener);
+    }
+
+    /**
+     * Gets the touchable region needed for heads up notifications. Returns null if no touchable
+     * region is required (ie: no heads up notification currently exists).
+     */
+    @Nullable Region getTouchableRegion() {
+        NotificationEntry topEntry = getTopEntry();
+
+        // This call could be made in an inconsistent state while the pinnedMode hasn't been
+        // updated yet, but callbacks leading out of the headsUp manager, querying it. Let's
+        // therefore also check if the topEntry is null.
+        if (!hasPinnedHeadsUp() || topEntry == null) {
+            return null;
+        } else {
+            if (topEntry.isChildInGroup()) {
+                final NotificationEntry groupSummary =
+                        mGroupManager.getGroupSummary(topEntry.getSbn());
+                if (groupSummary != null) {
+                    topEntry = groupSummary;
+                }
+            }
+            ExpandableNotificationRow topRow = topEntry.getRow();
+            int[] tmpArray = new int[2];
+            topRow.getLocationOnScreen(tmpArray);
+            int minX = tmpArray[0];
+            int maxX = tmpArray[0] + topRow.getWidth();
+            int height = topRow.getIntrinsicHeight();
+            mTouchableRegion.set(minX, 0, maxX, mHeadsUpInset + height);
+            return mTouchableRegion;
+        }
+    }
+
+    /**
      * Decides whether a click is invalid for a notification, i.e it has not been shown long enough
      * that a user might have consciously clicked on it.
      *
      * @param key the key of the touched notification
      * @return whether the touch is invalid and should be discarded
      */
-    public boolean shouldSwallowClick(@NonNull String key) {
+    boolean shouldSwallowClick(@NonNull String key) {
         HeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key);
         return entry != null && mClock.currentTimeMillis() < entry.mPostTime;
     }
@@ -213,39 +221,12 @@
      *
      * @param isExpanded True to notify expanded, false to notify collapsed.
      */
-    public void setIsPanelExpanded(boolean isExpanded) {
+    void setIsPanelExpanded(boolean isExpanded) {
         if (isExpanded != mIsExpanded) {
             mIsExpanded = isExpanded;
             if (isExpanded) {
                 mHeadsUpGoingAway = false;
             }
-            mStatusBarTouchableRegionManager.setIsStatusBarExpanded(isExpanded);
-            mStatusBarTouchableRegionManager.updateTouchableRegion();
-        }
-    }
-
-    @Override
-    public void onStateChanged(int newState) {
-        boolean wasKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
-        boolean isKeyguard = newState == StatusBarState.KEYGUARD;
-        mStatusBarState = newState;
-        if (wasKeyguard && !isKeyguard && mKeysToRemoveWhenLeavingKeyguard.size() != 0) {
-            String[] keys = mKeysToRemoveWhenLeavingKeyguard.toArray(new String[0]);
-            for (String key : keys) {
-                removeAlertEntry(key);
-            }
-            mKeysToRemoveWhenLeavingKeyguard.clear();
-        }
-    }
-
-    @Override
-    public void onDozingChanged(boolean isDozing) {
-        if (!isDozing) {
-            // Let's make sure all huns we got while dozing time out within the normal timeout
-            // duration. Otherwise they could get stuck for a very long time
-            for (AlertEntry entry : mAlertEntries.values()) {
-                entry.updateEntry(true /* updatePostTime */);
-            }
         }
     }
 
@@ -262,18 +243,16 @@
      * Set that we are exiting the headsUp pinned mode, but some notifications might still be
      * animating out. This is used to keep the touchable regions in a sane state.
      */
-    public void setHeadsUpGoingAway(boolean headsUpGoingAway) {
+    void setHeadsUpGoingAway(boolean headsUpGoingAway) {
         if (headsUpGoingAway != mHeadsUpGoingAway) {
             mHeadsUpGoingAway = headsUpGoingAway;
-            if (!headsUpGoingAway) {
-                mStatusBarTouchableRegionManager.updateTouchableRegionAfterLayout();
-            } else {
-                mStatusBarTouchableRegionManager.updateTouchableRegion();
+            for (OnHeadsUpPhoneListenerChange listener : mHeadsUpPhoneListeners) {
+                listener.onHeadsUpGoingAwayStateChanged(headsUpGoingAway);
             }
         }
     }
 
-    public boolean isHeadsUpGoingAway() {
+    boolean isHeadsUpGoingAway() {
         return mHeadsUpGoingAway;
     }
 
@@ -346,63 +325,6 @@
         dumpInternal(fd, pw, args);
     }
 
-    /**
-     * Update touch insets to include any area needed for touching a heads up notification.
-     *
-     * @param info Insets that will include heads up notification touch area after execution.
-     */
-    @Nullable
-    public void updateTouchableRegion(ViewTreeObserver.InternalInsetsInfo info) {
-        info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-        info.touchableRegion.set(calculateTouchableRegion());
-    }
-
-    public Region calculateTouchableRegion() {
-        NotificationEntry topEntry = getTopEntry();
-        // This call could be made in an inconsistent state while the pinnedMode hasn't been
-        // updated yet, but callbacks leading out of the headsUp manager, querying it. Let's
-        // therefore also check if the topEntry is null.
-        if (!hasPinnedHeadsUp() || topEntry == null) {
-            mTouchableRegion.set(0, 0, mNotificationShadeWindowView.getWidth(), mStatusBarHeight);
-            updateRegionForNotch(mTouchableRegion);
-
-        } else {
-            if (topEntry.isChildInGroup()) {
-                final NotificationEntry groupSummary =
-                        mGroupManager.getGroupSummary(topEntry.getSbn());
-                if (groupSummary != null) {
-                    topEntry = groupSummary;
-                }
-            }
-            ExpandableNotificationRow topRow = topEntry.getRow();
-            topRow.getLocationOnScreen(mTmpTwoArray);
-            int minX = mTmpTwoArray[0];
-            int maxX = mTmpTwoArray[0] + topRow.getWidth();
-            int height = topRow.getIntrinsicHeight();
-            mTouchableRegion.set(minX, 0, maxX, mHeadsUpInset + height);
-        }
-        return mTouchableRegion;
-    }
-
-    private void updateRegionForNotch(Region region) {
-        WindowInsets windowInsets = mNotificationShadeWindowView.getRootWindowInsets();
-        if (windowInsets == null) {
-            Log.w(TAG, "StatusBarWindowView is not attached.");
-            return;
-        }
-        DisplayCutout cutout = windowInsets.getDisplayCutout();
-        if (cutout == null) {
-            return;
-        }
-
-        // Expand touchable region such that we also catch touches that just start below the notch
-        // area.
-        Rect bounds = new Rect();
-        ScreenDecorations.DisplayCutoutView.boundsFromDirection(cutout, Gravity.TOP, bounds);
-        bounds.offset(0, mDisplayCutoutTouchableRegionSize);
-        region.union(bounds);
-    }
-
     @Override
     public boolean shouldExtendLifetime(NotificationEntry entry) {
         // We should not defer the removal if reordering isn't allowed since otherwise
@@ -411,11 +333,6 @@
         return mVisualStabilityManager.isReorderingAllowed() && super.shouldExtendLifetime(entry);
     }
 
-    @Override
-    public void onConfigChanged(Configuration newConfig) {
-        initResources();
-    }
-
     ///////////////////////////////////////////////////////////////////////////////////////////////
     //  VisualStabilityManager.Callback overrides:
 
@@ -522,8 +439,7 @@
                         // time out anyway
                         && !entry.showingPulsing()) {
                     mEntriesToRemoveWhenReorderingAllowed.add(entry);
-                    mVisualStabilityManager.addReorderingAllowedCallback(
-                            HeadsUpManagerPhone.this);
+                    mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManagerPhone.this);
                 } else if (mTrackingHeadsUp) {
                     mEntriesToRemoveAfterExpand.add(entry);
                 } else if (mIsAutoHeadsUp && mStatusBarState == StatusBarState.KEYGUARD) {
@@ -626,4 +542,42 @@
     public interface AnimationStateHandler {
         void setHeadsUpGoingAwayAnimationsAllowed(boolean allowed);
     }
+
+    /**
+     * Listener to register for HeadsUpNotification Phone changes.
+     */
+    public interface OnHeadsUpPhoneListenerChange {
+        /**
+         * Called when a heads up notification is 'going away' or no longer 'going away'.
+         * See {@link HeadsUpManagerPhone#setHeadsUpGoingAway}.
+         */
+        void onHeadsUpGoingAwayStateChanged(boolean headsUpGoingAway);
+    }
+
+    private final StateListener mStatusBarStateListener = new StateListener() {
+        @Override
+        public void onStateChanged(int newState) {
+            boolean wasKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
+            boolean isKeyguard = newState == StatusBarState.KEYGUARD;
+            mStatusBarState = newState;
+            if (wasKeyguard && !isKeyguard && mKeysToRemoveWhenLeavingKeyguard.size() != 0) {
+                String[] keys = mKeysToRemoveWhenLeavingKeyguard.toArray(new String[0]);
+                for (String key : keys) {
+                    removeAlertEntry(key);
+                }
+                mKeysToRemoveWhenLeavingKeyguard.clear();
+            }
+        }
+
+        @Override
+        public void onDozingChanged(boolean isDozing) {
+            if (!isDozing) {
+                // Let's make sure all huns we got while dozing time out within the normal timeout
+                // duration. Otherwise they could get stuck for a very long time
+                for (AlertEntry entry : mAlertEntries.values()) {
+                    entry.updateEntry(true /* updatePostTime */);
+                }
+            }
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index d2186f9..fb7976f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -453,10 +453,11 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor, MetricsLogger metricsLogger,
             ActivityManager activityManager, ZenModeController zenModeController,
             ConfigurationController configurationController,
-            FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
+            FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
         super(view, falsingManager, dozeLog, keyguardStateController,
                 (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
-                latencyTracker, flingAnimationUtilsBuilder);
+                latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
         mView = view;
         mMetricsLogger = metricsLogger;
         mActivityManager = activityManager;
@@ -704,9 +705,9 @@
 
     private Rect calculateGestureExclusionRect() {
         Rect exclusionRect = null;
-        Region touchableRegion = mHeadsUpManager.calculateTouchableRegion();
+        Region touchableRegion = mStatusBarTouchableRegionManager.calculateTouchableRegion();
         if (isFullyCollapsed() && touchableRegion != null) {
-            // Note: The heads up manager also calculates the non-pinned touchable region
+            // Note: The manager also calculates the non-pinned touchable region
             exclusionRect = touchableRegion.getBounds();
         }
         return exclusionRect != null ? exclusionRect : EMPTY_RECT;
@@ -1914,6 +1915,7 @@
         boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown;
         if (mPanelExpanded != isExpanded) {
             mHeadsUpManager.setIsPanelExpanded(isExpanded);
+            mStatusBarTouchableRegionManager.setPanelExpanded(isExpanded);
             mStatusBar.setPanelExpanded(isExpanded);
             mPanelExpanded = isExpanded;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index af46f7b..30367ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -73,6 +73,7 @@
 
     protected StatusBar mStatusBar;
     protected HeadsUpManagerPhone mHeadsUpManager;
+    protected final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
 
     private float mPeekHeight;
     private float mHintDistance;
@@ -206,7 +207,9 @@
             FalsingManager falsingManager, DozeLog dozeLog,
             KeyguardStateController keyguardStateController,
             SysuiStatusBarStateController statusBarStateController, VibratorHelper vibratorHelper,
-            LatencyTracker latencyTracker, FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
+            LatencyTracker latencyTracker,
+            FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
         mView = view;
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
@@ -251,6 +254,7 @@
                 R.bool.config_enableNotificationShadeDrag);
         mVibratorHelper = vibratorHelper;
         mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
+        mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
     }
 
     protected void loadDimens() {
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 301eac2..d8d96c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -356,6 +356,7 @@
     private final KeyguardBypassController mKeyguardBypassController;
     private final KeyguardStateController mKeyguardStateController;
     private final HeadsUpManagerPhone mHeadsUpManager;
+    private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
     private final DynamicPrivacyController mDynamicPrivacyController;
     private final BypassHeadsUpNotifier mBypassHeadsUpNotifier;
     private final FalsingManager mFalsingManager;
@@ -676,7 +677,8 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
-            DismissCallbackRegistry dismissCallbackRegistry) {
+            DismissCallbackRegistry dismissCallbackRegistry,
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
         super(context);
         mNotificationsController = notificationsController;
         mLightBarController = lightBarController;
@@ -688,6 +690,7 @@
         mKeyguardBypassController = keyguardBypassController;
         mKeyguardStateController = keyguardStateController;
         mHeadsUpManager = headsUpManagerPhone;
+        mStatusBarTouchableRegionManager = statusBarTouchableRegionManager;
         mDynamicPrivacyController = dynamicPrivacyController;
         mBypassHeadsUpNotifier = bypassHeadsUpNotifier;
         mFalsingManager = falsingManager;
@@ -1031,9 +1034,8 @@
                         CollapsedStatusBarFragment.TAG)
                 .commit();
 
-        mHeadsUpManager.setUp(mNotificationShadeWindowView, mGroupManager, this,
-                mVisualStabilityManager);
-        mConfigurationController.addCallback(mHeadsUpManager);
+        mHeadsUpManager.setup(mVisualStabilityManager);
+        mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
         mHeadsUpManager.addListener(this);
         mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener());
         mHeadsUpManager.addListener(mVisualStabilityManager);
@@ -2468,6 +2470,12 @@
             pw.println("  mHeadsUpManager: null");
         }
 
+        if (mStatusBarTouchableRegionManager != null) {
+            mStatusBarTouchableRegionManager.dump(fd, pw, args);
+        } else {
+            pw.println("  mStatusBarTouchableRegionManager: null");
+        }
+
         if (mLightBarController != null) {
             mLightBarController.dump(fd, pw, args);
         }
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 2485513..693cdd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -209,7 +209,7 @@
             };
 
             mViewHierarchyManager.setUpWithPresenter(this, notifListContainer);
-            mEntryManager.setUpWithPresenter(this, notifListContainer, mHeadsUpManager);
+            mEntryManager.setUpWithPresenter(this);
             mEntryManager.addNotificationEntryListener(notificationEntryListener);
             mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
             mEntryManager.addNotificationLifetimeExtender(mGutsManager);
@@ -230,8 +230,6 @@
             onUserSwitched(mLockscreenUserManager.getCurrentUserId());
         });
         Dependency.get(ConfigurationController.class).addCallback(this);
-
-        notificationAlertingManager.setHeadsUpManager(mHeadsUpManager);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index b8fb6d3..5bab867 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -17,66 +17,187 @@
 package com.android.systemui.statusbar.phone;
 
 import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.Log;
+import android.view.DisplayCutout;
+import android.view.Gravity;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
+import android.view.WindowInsets;
 
-import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.ScreenDecorations;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
 
 /**
- * Manages what parts of the status bar are touchable. Clients are primarily UI that displays in the
- * status bar even though the UI doesn't look like part of the status bar.
+ * Manages what parts of the status bar are touchable. Clients are primarily UI that display in the
+ * status bar even though the UI doesn't look like part of the status bar. Currently this
+ * includes HeadsUpNotifications and Bubbles.
  */
-public final class StatusBarTouchableRegionManager implements
-        OnComputeInternalInsetsListener, ConfigurationListener {
+@Singleton
+public final class StatusBarTouchableRegionManager implements Dumpable {
+    private static final String TAG = "TouchableRegionManager";
 
-    private final BubbleController mBubbleController = Dependency.get(BubbleController.class);
+    private final Context mContext;
     private final HeadsUpManagerPhone mHeadsUpManager;
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
+    private final BubbleController mBubbleController;
+
     private boolean mIsStatusBarExpanded = false;
     private boolean mShouldAdjustInsets = false;
-    private final StatusBar mStatusBar;
-    private final View mNotificationShadeWindowView;
+    private StatusBar mStatusBar;
+    private View mNotificationShadeWindowView;
     private View mNotificationPanelView;
     private boolean mForceCollapsedUntilLayout = false;
-    private final NotificationShadeWindowController mNotificationShadeWindowController;
 
-    public StatusBarTouchableRegionManager(HeadsUpManagerPhone headsUpManager,
-                                           @NonNull StatusBar statusBar,
-                                           @NonNull View notificationShadeWindowView) {
-        mHeadsUpManager = headsUpManager;
-        mStatusBar = statusBar;
-        mNotificationShadeWindowView = notificationShadeWindowView;
-        mNotificationShadeWindowController =
-                Dependency.get(NotificationShadeWindowController.class);
+    private Region mTouchableRegion = new Region();
+    private int mDisplayCutoutTouchableRegionSize;
+    private int mStatusBarHeight;
 
-        mBubbleController.setBubbleStateChangeListener((hasBubbles) -> {
-            updateTouchableRegion();
+    @Inject
+    public StatusBarTouchableRegionManager(
+            Context context,
+            NotificationShadeWindowController notificationShadeWindowController,
+            ConfigurationController configurationController,
+            HeadsUpManagerPhone headsUpManager,
+            BubbleController bubbleController
+    ) {
+        mContext = context;
+        initResources();
+        configurationController.addCallback(new ConfigurationListener() {
+            @Override
+            public void onDensityOrFontScaleChanged() {
+                initResources();
+            }
+
+            @Override
+            public void onOverlayChanged() {
+                initResources();
+            }
         });
 
+        mHeadsUpManager = headsUpManager;
+        mHeadsUpManager.addListener(
+                new OnHeadsUpChangedListener() {
+                    @Override
+                    public void onHeadsUpPinnedModeChanged(boolean hasPinnedNotification) {
+                        if (Log.isLoggable(TAG, Log.WARN)) {
+                            Log.w(TAG, "onHeadsUpPinnedModeChanged");
+                        }
+                        updateTouchableRegion();
+                    }
+                });
+        mHeadsUpManager.addHeadsUpPhoneListener(
+                new HeadsUpManagerPhone.OnHeadsUpPhoneListenerChange() {
+                    @Override
+                    public void onHeadsUpGoingAwayStateChanged(boolean headsUpGoingAway) {
+                        if (!headsUpGoingAway) {
+                            updateTouchableRegionAfterLayout();
+                        } else {
+                            updateTouchableRegion();
+                        }
+                    }
+                });
+
+        mNotificationShadeWindowController = notificationShadeWindowController;
         mNotificationShadeWindowController.setForcePluginOpenListener((forceOpen) -> {
             updateTouchableRegion();
         });
-        Dependency.get(ConfigurationController.class).addCallback(this);
-        if (mNotificationShadeWindowView != null) {
-            mNotificationPanelView = mNotificationShadeWindowView.findViewById(
-                    R.id.notification_panel);
+
+        mBubbleController = bubbleController;
+        mBubbleController.setBubbleStateChangeListener((hasBubbles) -> {
+            updateTouchableRegion();
+        });
+    }
+
+    protected void setup(
+            @NonNull StatusBar statusBar,
+            @NonNull View notificationShadeWindowView) {
+        mStatusBar = statusBar;
+        mNotificationShadeWindowView = notificationShadeWindowView;
+        mNotificationPanelView = mNotificationShadeWindowView.findViewById(R.id.notification_panel);
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("StatusBarTouchableRegionManager state:");
+        pw.print("  mTouchableRegion=");
+        pw.println(mTouchableRegion);
+    }
+
+    /**
+     * Notify that the status bar panel gets expanded or collapsed.
+     *
+     * @param isExpanded True to notify expanded, false to notify collapsed.
+     */
+    void setPanelExpanded(boolean isExpanded) {
+        if (isExpanded != mIsStatusBarExpanded) {
+            mIsStatusBarExpanded = isExpanded;
+            if (isExpanded) {
+                // make sure our state is sane
+                mForceCollapsedUntilLayout = false;
+            }
+            updateTouchableRegion();
         }
     }
 
     /**
+     * Calculates the touch region needed for heads up notifications, taking into consideration
+     * any existing display cutouts (notch)
+     * @return the heads up notification touch area
+     */
+    Region calculateTouchableRegion() {
+        // Update touchable region for HeadsUp notifications
+        final Region headsUpTouchableRegion = mHeadsUpManager.getTouchableRegion();
+        if (headsUpTouchableRegion != null) {
+            mTouchableRegion.set(headsUpTouchableRegion);
+        } else {
+            // If there aren't any HUNs, update the touch region to the status bar
+            // width/height, potentially adjusting for a display cutout (notch)
+            mTouchableRegion.set(0, 0, mNotificationShadeWindowView.getWidth(),
+                    mStatusBarHeight);
+            updateRegionForNotch(mTouchableRegion);
+        }
+
+        // Update touchable region for bubbles
+        Rect bubbleRect = mBubbleController.getTouchableRegion();
+        if (bubbleRect != null) {
+            mTouchableRegion.union(bubbleRect);
+        }
+        return mTouchableRegion;
+    }
+
+    private void initResources() {
+        Resources resources = mContext.getResources();
+        mDisplayCutoutTouchableRegionSize = resources.getDimensionPixelSize(
+                com.android.internal.R.dimen.display_cutout_touchable_region_size);
+        mStatusBarHeight =
+                resources.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
+    }
+
+    /**
      * Set the touchable portion of the status bar based on what elements are visible.
      */
-    public void updateTouchableRegion() {
+    private void updateTouchableRegion() {
         boolean hasCutoutInset = (mNotificationShadeWindowView != null)
                 && (mNotificationShadeWindowView.getRootWindowInsets() != null)
                 && (mNotificationShadeWindowView.getRootWindowInsets().getDisplayCutout() != null);
-        boolean shouldObserve =
-                mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpManager.isHeadsUpGoingAway()
+        boolean shouldObserve = mHeadsUpManager.hasPinnedHeadsUp()
+                        || mHeadsUpManager.isHeadsUpGoingAway()
                         || mBubbleController.hasBubbles()
                         || mForceCollapsedUntilLayout
                         || hasCutoutInset
@@ -87,11 +208,11 @@
 
         if (shouldObserve) {
             mNotificationShadeWindowView.getViewTreeObserver()
-                    .addOnComputeInternalInsetsListener(this);
+                    .addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
             mNotificationShadeWindowView.requestLayout();
         } else {
             mNotificationShadeWindowView.getViewTreeObserver()
-                    .removeOnComputeInternalInsetsListener(this);
+                    .removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
         }
         mShouldAdjustInsets = shouldObserve;
     }
@@ -99,7 +220,7 @@
     /**
      * Calls {@code updateTouchableRegion()} after a layout pass completes.
      */
-    public void updateTouchableRegionAfterLayout() {
+    private void updateTouchableRegionAfterLayout() {
         if (mNotificationPanelView != null) {
             mForceCollapsedUntilLayout = true;
             mNotificationPanelView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@@ -116,34 +237,38 @@
         }
     }
 
-    /**
-     * Notify that the status bar panel gets expanded or collapsed.
-     *
-     * @param isExpanded True to notify expanded, false to notify collapsed.
-     */
-    public void setIsStatusBarExpanded(boolean isExpanded) {
-        if (isExpanded != mIsStatusBarExpanded) {
-            mIsStatusBarExpanded = isExpanded;
-            if (isExpanded) {
-                // make sure our state is sane
-                mForceCollapsedUntilLayout = false;
-            }
-            updateTouchableRegion();
+    private void updateRegionForNotch(Region touchableRegion) {
+        WindowInsets windowInsets = mNotificationShadeWindowView.getRootWindowInsets();
+        if (windowInsets == null) {
+            Log.w(TAG, "StatusBarWindowView is not attached.");
+            return;
         }
-    }
-
-    @Override
-    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
-        if (mIsStatusBarExpanded || mStatusBar.isBouncerShowing()) {
-            // The touchable region is always the full area when expanded
+        DisplayCutout cutout = windowInsets.getDisplayCutout();
+        if (cutout == null) {
             return;
         }
 
-        mHeadsUpManager.updateTouchableRegion(info);
-
-        Rect bubbleRect = mBubbleController.getTouchableRegion();
-        if (bubbleRect != null) {
-            info.touchableRegion.union(bubbleRect);
-        }
+        // Expand touchable region such that we also catch touches that just start below the notch
+        // area.
+        Rect bounds = new Rect();
+        ScreenDecorations.DisplayCutoutView.boundsFromDirection(cutout, Gravity.TOP, bounds);
+        bounds.offset(0, mDisplayCutoutTouchableRegionSize);
+        touchableRegion.union(bounds);
     }
+
+    private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener =
+            new OnComputeInternalInsetsListener() {
+        @Override
+        public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
+            if (mIsStatusBarExpanded || mStatusBar.isBouncerShowing()) {
+                // The touchable region is always the full area when expanded
+                return;
+            }
+
+            // Update touch insets to include any area needed for touching features that live in
+            // the status bar (ie: heads up notifications or bubbles)
+            info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+            info.touchableRegion.set(calculateTouchableRegion());
+        }
+    };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 26459a9..e64f821 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -85,6 +85,7 @@
 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.StatusBarTouchableRegionManager;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -192,7 +193,8 @@
             KeyguardDismissUtil keyguardDismissUtil,
             ExtensionController extensionController,
             UserInfoControllerImpl userInfoControllerImpl,
-            DismissCallbackRegistry dismissCallbackRegistry) {
+            DismissCallbackRegistry dismissCallbackRegistry,
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
         return new StatusBar(
                 context,
                 notificationsController,
@@ -267,6 +269,7 @@
                 keyguardDismissUtil,
                 extensionController,
                 userInfoControllerImpl,
-                dismissCallbackRegistry);
+                dismissCallbackRegistry,
+                statusBarTouchableRegionManager);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/Assert.java b/packages/SystemUI/src/com/android/systemui/util/Assert.java
index f6e921e..3f05657 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Assert.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Assert.java
@@ -24,12 +24,17 @@
  * Helper providing common assertions.
  */
 public class Assert {
+    private static final Looper sMainLooper = Looper.getMainLooper();
+    private static Looper sTestLooper = null;
 
     @VisibleForTesting
-    public static Looper sMainLooper = Looper.getMainLooper();
+    public static void setTestableLooper(Looper testLooper) {
+        sTestLooper = testLooper;
+    }
 
     public static void isMainThread() {
-        if (!sMainLooper.isCurrentThread()) {
+        if (!sMainLooper.isCurrentThread()
+                && (sTestLooper == null || !sTestLooper.isCurrentThread())) {
             throw new IllegalStateException("should be called from the main thread."
                     + " sMainLooper.threadName=" + sMainLooper.getThread().getName()
                     + " Thread.currentThread()=" + Thread.currentThread().getName());
@@ -37,7 +42,8 @@
     }
 
     public static void isNotMainThread() {
-        if (sMainLooper.isCurrentThread()) {
+        if (sMainLooper.isCurrentThread()
+                && (sTestLooper == null || sTestLooper.isCurrentThread())) {
             throw new IllegalStateException("should not be called from the main thread.");
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
index 082782d..b6ca8d8e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPresentationTest.java
@@ -37,7 +37,7 @@
     @Test
     public void testInflation_doesntCrash() {
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
-        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         InjectionInflationController inflationController = new InjectionInflationController(
                 SystemUIFactory.getInstance().getRootComponent());
         Context context = getContext();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 116f8fc..462b042 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -19,7 +19,6 @@
 import android.net.Uri;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
 
@@ -51,7 +50,7 @@
 
     @Before
     public void setUp() throws Exception {
-        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         InjectionInflationController inflationController = new InjectionInflationController(
                 SystemUIFactory.getInstance().getRootComponent());
         LayoutInflater layoutInflater = inflationController
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
index e4b83cc..bc3c3d9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
@@ -20,14 +20,12 @@
 
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.LayoutInflater;
 
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.Assert;
 import com.android.systemui.util.InjectionInflationController;
 
 import org.junit.Before;
@@ -50,7 +48,7 @@
 
     @Before
     public void setUp() {
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         InjectionInflationController inflationController = new InjectionInflationController(
                 SystemUIFactory.getInstance().getRootComponent());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 7e4ba92..befe3e1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -147,6 +147,7 @@
         context.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
 
         mTestableLooper = TestableLooper.get(this);
+        allowTestableLooperAsMainThread();
         mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(context);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index ffe8c28..471149c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -23,7 +23,6 @@
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.annotation.UiThreadTest;
@@ -33,7 +32,6 @@
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.util.Assert;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -52,7 +50,7 @@
     public void setUp() throws Exception {
         mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
         mDependency.injectMockDependency(NotificationMediaManager.class);
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         Context context = getContext();
         mRow = new NotificationTestHelper(context, mDependency).createRow();
         mCallback = mock(ExpandHelper.Callback.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 8d11b54..c912b67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -35,7 +35,6 @@
 import android.app.NotificationManager;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.Looper;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
@@ -75,8 +74,8 @@
 
     @Before
     public void setUp() throws Exception {
-        // assume the TestLooper is the main looper for these tests
-        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        // allow the TestLooper to be asserted as the main thread these tests
+        allowTestableLooperAsMainThread();
 
         MockitoAnnotations.initMocks(this);
         mFsc = new ForegroundServiceController(mEntryManager, mAppOpsController, mMainHandler);
@@ -93,7 +92,7 @@
     public void testAppOpsChangedCalledFromBgThread() {
         try {
             // WHEN onAppOpChanged is called from a different thread than the MainLooper
-            com.android.systemui.util.Assert.sMainLooper = Looper.getMainLooper();
+            disallowTestableLooperAsMainThread();
             NotificationEntry entry = createFgEntry();
             mFsc.onAppOpChanged(
                     AppOpsManager.OP_CAMERA,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index fc331d6..689eed9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -77,7 +77,7 @@
 
     @Before
     public void setUp() throws Exception {
-        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
         mEventCountdown = new CountDownLatch(1);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index c85d600..7ac5443 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -25,6 +25,7 @@
 import android.os.ParcelFileDescriptor;
 import android.testing.DexmakerShareClassLoaderRule;
 import android.testing.LeakCheck;
+import android.testing.TestableLooper;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -32,7 +33,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.util.Assert;
 
 import org.junit.After;
 import org.junit.Before;
@@ -86,11 +86,24 @@
     public void SysuiTeardown() {
         InstrumentationRegistry.registerInstance(mRealInstrumentation,
                 InstrumentationRegistry.getArguments());
-        // Reset the assert's main looper.
-        Assert.sMainLooper = Looper.getMainLooper();
+        // Reset the assert's testable looper to null.
+        disallowTestableLooperAsMainThread();
         SystemUIFactory.cleanup();
     }
 
+    /**
+     * Tests are run on the TestableLooper; however, there are parts of SystemUI that assert that
+     * the code is run from the main looper. Therefore, we allow the TestableLooper to pass these
+     * assertions since in a test, the TestableLooper is essentially the MainLooper.
+     */
+    protected void allowTestableLooperAsMainThread() {
+        com.android.systemui.util.Assert.setTestableLooper(TestableLooper.get(this).getLooper());
+    }
+
+    protected void disallowTestableLooperAsMainThread() {
+        com.android.systemui.util.Assert.setTestableLooper(null);
+    }
+
     protected LeakCheck getLeakCheck() {
         return null;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index 6d83ac3..644ed3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -85,7 +85,7 @@
                 Dependency.get(NotificationLockscreenUserManager.class);
         NotificationViewHierarchyManager viewHierarchyManager =
                 Dependency.get(NotificationViewHierarchyManager.class);
-        entryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager);
+        entryManager.setUpWithPresenter(mPresenter);
         entryManager.addNotificationEntryListener(mEntryListener);
         gutsManager.setUpWithPresenter(mPresenter, mListContainer,
                 mCheckSaveListener, mOnSettingsClickListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 60163f2..8e87e0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -54,7 +54,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.util.Assert;
 
 import com.google.android.collect.Lists;
 
@@ -90,7 +89,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
-        Assert.sMainLooper = mTestableLooper.getLooper();
+        allowTestableLooperAsMainThread();
         mHandler = Handler.createAsync(mTestableLooper.getLooper());
 
         mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
index 9d667a9..0a38f16 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
@@ -21,7 +21,6 @@
 
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.widget.FrameLayout;
 
@@ -46,7 +45,7 @@
 
     @Before
     public void setUp() throws Exception {
-        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         mNotificationTestHelper = new NotificationTestHelper(getContext(), mDependency);
         mHostLayout = new FrameLayout(getContext());
         mObserver = new AboveShelfObserver(mHostLayout);
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 f1fba79..6a6e5c8 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
@@ -98,7 +98,6 @@
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.util.Assert;
 import com.android.systemui.util.leak.LeakDetector;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -205,7 +204,7 @@
 
         mCountDownLatch = new CountDownLatch(1);
 
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
                 Handler.createAsync(TestableLooper.get(this).getLooper()));
         when(mRemoteInputManager.getController()).thenReturn(mRemoteInputController);
@@ -256,7 +255,7 @@
                 mLeakDetector,
                 mock(ForegroundServiceDismissalFeatureController.class)
         );
-        mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager);
+        mEntryManager.setUpWithPresenter(mPresenter);
         mEntryManager.addNotificationEntryListener(mEntryListener);
         mEntryManager.addNotificationRemoveInterceptor(mRemoveInterceptor);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index 1116a33..97e0a31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -32,7 +32,6 @@
 import android.os.Bundle;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.annotation.UiThreadTest;
@@ -79,7 +78,7 @@
 
     @Before
     public void setUp() throws Exception {
-        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
         when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL);
 
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 0e730e5..d522f90 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
@@ -22,11 +22,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
 import com.android.systemui.statusbar.phone.NotificationGroupManager
 import com.android.systemui.util.leak.LeakDetector
-
 import java.util.concurrent.CountDownLatch
 
 /**
@@ -53,11 +50,9 @@
     }
 
     fun setUpForTest(
-        presenter: NotificationPresenter?,
-        listContainer: NotificationListContainer?,
-        headsUpManager: HeadsUpManagerPhone?
+        presenter: NotificationPresenter?
     ) {
-        super.setUpWithPresenter(presenter, listContainer, headsUpManager)
+        super.setUpWithPresenter(presenter)
     }
 
     fun setActiveNotificationList(activeList: List<NotificationEntry>) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 12e9d31..605b59e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -77,7 +77,6 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
-import com.android.systemui.util.Assert;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -129,7 +128,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
 
         when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(true);
         when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 27ca18c..e570ab8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -51,7 +51,6 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
-import com.android.systemui.util.Assert;
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
@@ -101,7 +100,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
 
         mListBuilder = new ShadeListBuilder(mSystemClock, mLogger, mock(DumpController.class));
         mListBuilder.setOnRenderListListener(mOnRenderListListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
index eb1af7c..67b1aad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
@@ -44,7 +44,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
-import com.android.systemui.util.Assert;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
@@ -84,7 +83,7 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
 
         mForegroundCoordinator =
                 new ForegroundCoordinator(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index a891810..e960185 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -39,7 +39,6 @@
 import android.app.AppOpsManager;
 import android.app.NotificationChannel;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.util.ArraySet;
 import android.view.NotificationHeaderView;
@@ -79,7 +78,7 @@
 
     @Before
     public void setUp() throws Exception {
-        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
         mGroupRow = mNotificationTestHelper.createGroup();
         mGroupRow.setHeadsUpAnimatingAwayListener(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
index a8c438a..481bac2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
@@ -49,7 +49,6 @@
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.util.Assert;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -75,7 +74,7 @@
 
     @Before
     public void setUp() {
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
         mDependency.injectMockDependency(BubbleController.class);
         when(mGutsManager.openGuts(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index bbb6723..54c0bde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -74,7 +74,6 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.util.Assert;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -118,7 +117,7 @@
     @Before
     public void setUp() {
         mTestableLooper = TestableLooper.get(this);
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         mDependency.injectTestDependency(DeviceProvisionedController.class,
                 mDeviceProvisionedController);
         mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 9a52ee8..5a89fc4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -56,6 +56,7 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -102,8 +103,8 @@
         mStatusBarStateController = mock(StatusBarStateController.class);
         mGroupManager = new NotificationGroupManager(mStatusBarStateController);
         mHeadsUpManager = new HeadsUpManagerPhone(mContext, mStatusBarStateController,
-                mock(KeyguardBypassController.class));
-        mHeadsUpManager.setUp(null, mGroupManager, null, null);
+                mock(KeyguardBypassController.class), mock(NotificationGroupManager.class),
+                mock(ConfigurationControllerImpl.class));
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
 
         NotificationContentInflater contentBinder = new NotificationContentInflater(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index 0790cb7..b661b28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.row.wrapper;
 
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
 import android.widget.RemoteViews;
@@ -43,7 +42,7 @@
 
     @Before
     public void setUp() throws Exception {
-        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         mRow = new NotificationTestHelper(mContext, mDependency).createRow();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
index 038eff7..69e4f22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
@@ -26,7 +26,6 @@
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
 import android.widget.RemoteViews;
@@ -64,7 +63,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
 
         mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
index 9567f33..830e8d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
@@ -20,7 +20,6 @@
 
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
 import android.widget.LinearLayout;
@@ -31,7 +30,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.util.Assert;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -48,7 +46,7 @@
 
     @Before
     public void setup() throws Exception {
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         mView = mock(View.class);
         mRow = new NotificationTestHelper(getContext(), mDependency).createRow();
         mNotificationViewWrapper = new TestableNotificationViewWrapper(mContext, mView, mRow);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 1773175..a2029c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.stack;
 
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.NotificationHeaderView;
 import android.view.View;
@@ -44,7 +43,7 @@
 
     @Before
     public void setUp() throws Exception {
-        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
         mGroup = mNotificationTestHelper.createGroup();
         mChildrenContainer = mGroup.getChildrenContainer();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 2d1bc78..ba2b946 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -24,7 +24,6 @@
 import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.filters.SmallTest;
@@ -66,7 +65,7 @@
         mRoundnessManager = new NotificationRoundnessManager(
                 mBypassController,
                 new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext));
-        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency);
         mFirst = testHelper.createRow();
         mFirst.setHeadsUpAnimatingAwayListener(animatingAway
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 9ccee75..0cb6585 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
@@ -144,7 +144,7 @@
     @Before
     @UiThreadTest
     public void setUp() throws Exception {
-        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
 
         mOriginalInterruptionModelSetting = Settings.Secure.getInt(mContext.getContentResolver(),
                 NOTIFICATION_NEW_INTERRUPTION_MODEL, 0);
@@ -185,7 +185,7 @@
                 mock(LeakDetector.class),
                 mock(ForegroundServiceDismissalFeatureController.class)
         );
-        mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager);
+        mEntryManager.setUpForTest(mock(NotificationPresenter.class));
         when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
 
         NotificationShelf notificationShelf = mock(NotificationShelf.class);
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 f71d0fc..a74657e 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
@@ -23,7 +23,6 @@
 import static org.mockito.Mockito.when;
 
 import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
 import android.widget.TextView;
@@ -69,7 +68,7 @@
 
     @Before
     public void setUp() throws Exception {
-        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency);
         mFirst = testHelper.createRow();
         mDependency.injectTestDependency(DarkIconDispatcher.class, mDarkIconDispatcher);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 50d8bf0b0..6d642ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -62,16 +62,21 @@
     @Mock private StatusBar mBar;
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private KeyguardBypassController mBypassController;
+    @Mock private ConfigurationControllerImpl mConfigurationController;
     private boolean mLivesPastNormalTime;
 
     private final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
-        TestableHeadsUpManagerPhone(Context context, View notificationShadeWindowView,
-                NotificationGroupManager groupManager, StatusBar bar,
+        TestableHeadsUpManagerPhone(
+                Context context,
+                NotificationGroupManager groupManager,
                 VisualStabilityManager vsManager,
                 StatusBarStateController statusBarStateController,
-                KeyguardBypassController keyguardBypassController) {
-            super(context, statusBarStateController, keyguardBypassController);
-            setUp(notificationShadeWindowView, groupManager, bar, vsManager);
+                KeyguardBypassController keyguardBypassController,
+                ConfigurationController configurationController
+        ) {
+            super(context, statusBarStateController, keyguardBypassController,
+                    groupManager, configurationController);
+            setup(vsManager);
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
         }
@@ -91,8 +96,8 @@
         mDependency.injectMockDependency(BubbleController.class);
         mDependency.injectMockDependency(NotificationShadeWindowController.class);
         mDependency.injectMockDependency(ConfigurationController.class);
-        mHeadsUpManager = new TestableHeadsUpManagerPhone(mContext, mNotificationShadeWindowView,
-                mGroupManager, mBar, mVSManager, mStatusBarStateController, mBypassController);
+        mHeadsUpManager = new TestableHeadsUpManagerPhone(mContext, mGroupManager, mVSManager,
+                mStatusBarStateController, mBypassController, mConfigurationController);
         super.setUp();
         mHeadsUpManager.mHandler = mTestHandler;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index 67b8e07..35971bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -96,7 +96,7 @@
 
     @Before
     public void setup() {
-        com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
         mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
         mDependency.injectTestDependency(KeyguardSecurityModel.class, mKeyguardSecurityModel);
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 5fb7159..13bf38c 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
@@ -138,6 +138,8 @@
     @Mock
     private NotificationEntryManager mNotificationEntryManager;
     @Mock
+    private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
+    @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
     private DozeLog mDozeLog;
@@ -221,7 +223,7 @@
                 mDozeParameters, mCommandQueue, mVibratorHelper,
                 mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
                 mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
-                mFlingAnimationUtilsBuilder);
+                mFlingAnimationUtilsBuilder, mStatusBarTouchableRegionManager);
         mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
                 mNotificationShelf, mNotificationAreaController, mScrimController);
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
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 db17a6e..e5ee439 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
@@ -236,6 +236,7 @@
     @Mock private LightsOutNotifController mLightsOutNotifController;
     @Mock private ViewMediatorCallback mViewMediatorCallback;
     @Mock private DismissCallbackRegistry mDismissCallbackRegistry;
+    @Mock private StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
     @Mock private ScreenPinningRequest mScreenPinningRequest;
     @Mock private LockscreenLockIconController mLockscreenLockIconController;
     @Mock private StatusBarNotificationActivityStarter.Builder
@@ -397,7 +398,8 @@
                 mKeyguardDismissUtil,
                 mExtensionController,
                 mUserInfoControllerImpl,
-                mDismissCallbackRegistry);
+                mDismissCallbackRegistry,
+                mStatusBarTouchableRegionManager);
 
         when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
                 mLockIconContainer);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index df62254..86add98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -43,7 +43,6 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.util.Assert;
 
 import org.junit.After;
 import org.junit.Before;
@@ -74,7 +73,7 @@
 
     @Before
     public void setUp() throws Exception {
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
 
         mDependency.injectTestDependency(RemoteInputQuickSettingsDisabler.class,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java
index 20dcbb7..1ff9548 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java
@@ -21,14 +21,12 @@
 import static org.mockito.Mockito.verify;
 
 import android.animation.Animator;
-import android.os.Looper;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.Assert;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -45,7 +43,7 @@
 
     @Before
     public void setup() {
-        Assert.sMainLooper = TestableLooper.get(this).getLooper();
+        allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
         KeepAwakeAnimationListener.sWakeLock = mWakeLock;
         mKeepAwakeAnimationListener = new KeepAwakeAnimationListener(getContext());
@@ -63,7 +61,10 @@
 
     @Test(expected = IllegalStateException.class)
     public void initThrows_onNonMainThread() {
-        Assert.sMainLooper = Looper.getMainLooper();
+        disallowTestableLooperAsMainThread();
+
+        // we are creating the KeepAwakeAnimationListener from the TestableLooper, not the main
+        // looper, so we expect an IllegalStateException:
         new KeepAwakeAnimationListener(getContext());
     }
 }
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index bfa962a..fd9f713 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -212,7 +212,7 @@
         new Thread(() -> {
             while (true) {
                 try {
-                    Thread.sleep(200);
+                    Thread.sleep(CONNECTOR_POLL_INTERVAL_MILLIS);
                 } catch (InterruptedException e) {
                     // Not much to do here, the system needs to wait for the connector
                 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 697e5d7..f544517 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2624,6 +2624,8 @@
                 mService.getServicePackageName(), mComponentName,
                 serviceLabel, serviceIcon, this, id, mCompatMode);
 
+        mService.logDatasetShown(id, mClientState);
+
         synchronized (mLock) {
             if (mUiShownTime == 0) {
                 // Log first time UI is shown.
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 63d0924..fde654d 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -69,6 +69,25 @@
     public static final int PACKAGE_COMPANION = 14;
     public static final int PACKAGE_RETAIL_DEMO = 15;
 
+    @IntDef(flag = true, prefix = "RESOLVE_", value = {
+            RESOLVE_NON_BROWSER_ONLY,
+            RESOLVE_NON_RESOLVER_ONLY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PrivateResolveFlags {}
+
+    /**
+     * Internal {@link #resolveIntent(Intent, String, int, int, int, boolean, int)} flag:
+     * only match components that contain a generic web intent filter.
+     */
+    public static final int RESOLVE_NON_BROWSER_ONLY = 0x00000001;
+
+    /**
+     * Internal {@link #resolveIntent(Intent, String, int, int, int, boolean, int)} flag: do not
+     * match to the resolver.
+     */
+    public static final int RESOLVE_NON_RESOLVER_ONLY = 0x00000002;
+
     @IntDef(value = {
             INTEGRITY_VERIFICATION_ALLOW,
             INTEGRITY_VERIFICATION_REJECT,
@@ -507,7 +526,8 @@
      * Resolves an activity intent, allowing instant apps to be resolved.
      */
     public abstract ResolveInfo resolveIntent(Intent intent, String resolvedType,
-            int flags, int userId, boolean resolveForStart, int filterCallingUid);
+            int flags, @PrivateResolveFlags int privateResolveFlags, int userId,
+            boolean resolveForStart, int filterCallingUid);
 
     /**
     * Resolves a service intent, allowing instant apps to be resolved.
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 0f36260..7083281 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4791,7 +4791,7 @@
                 return false;
             }
 
-            return vpn.startAlwaysOnVpn();
+            return vpn.startAlwaysOnVpn(mKeyStore);
         }
     }
 
@@ -4806,7 +4806,7 @@
                 Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                 return false;
             }
-            return vpn.isAlwaysOnPackageSupported(packageName);
+            return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
         }
     }
 
@@ -4827,11 +4827,11 @@
                 Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                 return false;
             }
-            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist)) {
+            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) {
                 return false;
             }
             if (!startAlwaysOnVpn(userId)) {
-                vpn.setAlwaysOnPackage(null, false, null);
+                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
                 return false;
             }
         }
@@ -5017,7 +5017,7 @@
                 loge("Starting user already has a VPN");
                 return;
             }
-            userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, userId);
+            userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, userId, mKeyStore);
             mVpns.put(userId, userVpn);
             if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
                 updateLockdownVpn();
@@ -5088,7 +5088,7 @@
             if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
                 Slog.d(TAG, "Restarting always-on VPN package " + packageName + " for user "
                         + userId);
-                vpn.startAlwaysOnVpn();
+                vpn.startAlwaysOnVpn(mKeyStore);
             }
         }
     }
@@ -5110,7 +5110,7 @@
             if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
                 Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user "
                         + userId);
-                vpn.setAlwaysOnPackage(null, false, null);
+                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 97b5eaa..430a5b9 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -79,6 +79,7 @@
 import android.os.TransactionTooLargeException;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -187,6 +188,9 @@
 
     AppWidgetManagerInternal mAppWidgetManagerInternal;
 
+    // white listed packageName.
+    ArraySet<String> mWhiteListAllowWhileInUsePermissionInFgs = new ArraySet<>();
+
     final Runnable mLastAnrDumpClearer = new Runnable() {
         @Override public void run() {
             synchronized (mAm) {
@@ -389,6 +393,20 @@
         AppStateTracker ast = LocalServices.getService(AppStateTracker.class);
         ast.addListener(new ForcedStandbyListener());
         mAppWidgetManagerInternal = LocalServices.getService(AppWidgetManagerInternal.class);
+        setWhiteListAllowWhileInUsePermissionInFgs();
+    }
+
+    private void setWhiteListAllowWhileInUsePermissionInFgs() {
+        final String attentionServicePackageName =
+                mAm.mContext.getPackageManager().getAttentionServicePackageName();
+        if (!TextUtils.isEmpty(attentionServicePackageName)) {
+            mWhiteListAllowWhileInUsePermissionInFgs.add(attentionServicePackageName);
+        }
+        final String systemCaptionsServicePackageName =
+                mAm.mContext.getPackageManager().getSystemCaptionsServicePackageName();
+        if (!TextUtils.isEmpty(systemCaptionsServicePackageName)) {
+            mWhiteListAllowWhileInUsePermissionInFgs.add(systemCaptionsServicePackageName);
+        }
     }
 
     ServiceRecord getServiceByNameLocked(ComponentName name, int callingUser) {
@@ -4634,6 +4652,12 @@
             return true;
         }
 
+        final boolean isWhiteListedPackage =
+                mWhiteListAllowWhileInUsePermissionInFgs.contains(callingPackage);
+        if (isWhiteListedPackage) {
+            return true;
+        }
+
         r.mInfoDenyWhileInUsePermissionInFgs =
                 "Background FGS start while-in-use permission restriction [callingPackage: "
                 + callingPackage
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index fabe92db..8fbe923 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -19,12 +19,16 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER_QUICK;
 
 import android.app.ActivityThread;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.OnPropertiesChangedListener;
 import android.provider.DeviceConfig.Properties;
@@ -33,6 +37,7 @@
 import android.util.ArraySet;
 import android.util.KeyValueListParser;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -289,6 +294,12 @@
     // started, the restriction is on while-in-use permissions.)
     volatile boolean mFlagBackgroundFgsStartRestrictionEnabled = true;
 
+    /**
+     * UserId to Assistant ComponentName mapping.
+     * Per user Assistant ComponentName is from {@link android.provider.Settings.Secure#ASSISTANT}
+     */
+    SparseArray<ComponentName> mAssistants = new SparseArray<>();
+
     private final ActivityManagerService mService;
     private ContentResolver mResolver;
     private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -364,6 +375,8 @@
                 Settings.Global.getUriFor(
                         Settings.Global.FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED);
 
+    private static final Uri ASSISTANT_URI = Settings.Secure.getUriFor(Settings.Secure.ASSISTANT);
+
     private static final Uri ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI =
             Settings.Global.getUriFor(Settings.Global.ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS);
 
@@ -430,6 +443,8 @@
         mResolver.registerContentObserver(ACTIVITY_STARTS_LOGGING_ENABLED_URI, false, this);
         mResolver.registerContentObserver(FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED_URI,
                 false, this);
+        mResolver.registerContentObserver(ASSISTANT_URI, false, this,
+                UserHandle.USER_ALL);
         if (mSystemServerAutomaticHeapDumpEnabled) {
             mResolver.registerContentObserver(ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI,
                     false, this);
@@ -445,6 +460,7 @@
         // The following read from Settings.
         updateActivityStartsLoggingEnabled();
         updateForegroundServiceStartsLoggingEnabled();
+        updateAssistant();
     }
 
     private void loadDeviceConfigConstants() {
@@ -476,6 +492,8 @@
             updateForegroundServiceStartsLoggingEnabled();
         } else if (ENABLE_AUTOMATIC_SYSTEM_SERVER_HEAP_DUMPS_URI.equals(uri)) {
             updateEnableAutomaticSystemServerHeapDumps();
+        } else if (ASSISTANT_URI.equals(uri)) {
+            updateAssistant();
         }
     }
 
@@ -573,6 +591,31 @@
                 Settings.Global.FOREGROUND_SERVICE_STARTS_LOGGING_ENABLED, 1) == 1;
     }
 
+    private void updateAssistant() {
+        final List<UserInfo> users =
+                mService.mContext.getSystemService(UserManager.class).getUsers();
+        SparseArray<ComponentName> componentNames = new SparseArray<>();
+        for (int i = 0; i < users.size(); i++) {
+            final int userId = users.get(i).id;
+            final String str = Settings.Secure.getStringForUser(mResolver,
+                    Settings.Secure.ASSISTANT, userId);
+            if (!TextUtils.isEmpty(str)) {
+                componentNames.put(userId, ComponentName.unflattenFromString(str));
+            }
+        }
+        synchronized (mService) {
+            for (int i = 0; i < mAssistants.size(); i++) {
+                mService.mServices.mWhiteListAllowWhileInUsePermissionInFgs.remove(
+                        mAssistants.valueAt(i).getPackageName());
+            }
+            mAssistants = componentNames;
+            for (int i = 0; i < mAssistants.size(); i++) {
+                mService.mServices.mWhiteListAllowWhileInUsePermissionInFgs.add(
+                        mAssistants.valueAt(i).getPackageName());
+            }
+        }
+    }
+
     private void updateBackgroundFgsStartsRestriction() {
         mFlagBackgroundFgsStartRestrictionEnabled = DeviceConfig.getBoolean(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -581,9 +624,6 @@
     }
 
     private void updateOomAdjUpdatePolicy() {
-
-
-
         OOMADJ_UPDATE_QUICK = DeviceConfig.getInt(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                 KEY_OOMADJ_UPDATE_POLICY,
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a651d9d..a2ae678 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1994,12 +1994,11 @@
         if (app.hasForegroundServices()) {
             capability |= capabilityFromFGS;
         } else if (!ActivityManager.isProcStateBackground(procState)) {
-            // procState higher than PROCESS_STATE_TRANSIENT_BACKGROUND implicitly has
+            // procState higher than PROCESS_STATE_BOUND_FOREGROUND_SERVICE implicitly has
             // camera/microphone capability
             if (procState == PROCESS_STATE_FOREGROUND_SERVICE && procStateFromFGSClient) {
                 // if the FGS state is passed down from client, do not grant implicit capabilities.
             } else {
-                //TODO: remove this line when enforcing the feature.
                 capability |= PROCESS_CAPABILITY_ALL_IMPLICIT;
             }
         }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index a934f22..40acfe1 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -536,9 +536,8 @@
                                 // The FGS has the location capability, but due to FGS BG start
                                 // restriction it lost the capability, use temp location capability
                                 // to mark this case.
-                                // TODO change to MODE_IGNORED when enforcing the feature.
                                 maybeShowWhileInUseDebugToast(op, mode);
-                                return AppOpsManager.MODE_ALLOWED;
+                                return AppOpsManager.MODE_IGNORED;
                             } else {
                                 return AppOpsManager.MODE_IGNORED;
                             }
@@ -546,17 +545,15 @@
                             if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
                                 return AppOpsManager.MODE_ALLOWED;
                             } else {
-                                //TODO change to MODE_IGNORED when enforcing the feature.
                                 maybeShowWhileInUseDebugToast(op, mode);
-                                return AppOpsManager.MODE_ALLOWED;
+                                return AppOpsManager.MODE_IGNORED;
                             }
                         case OP_RECORD_AUDIO:
                             if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
                                 return AppOpsManager.MODE_ALLOWED;
                             } else {
-                                //TODO change to MODE_IGNORED when enforcing the feature.
                                 maybeShowWhileInUseDebugToast(op, mode);
-                                return AppOpsManager.MODE_ALLOWED;
+                                return AppOpsManager.MODE_IGNORED;
                             }
                         default:
                             return AppOpsManager.MODE_ALLOWED;
@@ -571,17 +568,15 @@
                         if ((capability & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0) {
                             return AppOpsManager.MODE_ALLOWED;
                         } else {
-                            //TODO change to MODE_IGNORED when enforcing the feature.
                             maybeShowWhileInUseDebugToast(op, mode);
-                            return AppOpsManager.MODE_ALLOWED;
+                            return AppOpsManager.MODE_IGNORED;
                         }
                     case OP_RECORD_AUDIO:
                         if ((capability & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0) {
                             return AppOpsManager.MODE_ALLOWED;
                         } else {
-                            //TODO change to MODE_IGNORED when enforcing the feature.
                             maybeShowWhileInUseDebugToast(op, mode);
-                            return AppOpsManager.MODE_ALLOWED;
+                            return AppOpsManager.MODE_IGNORED;
                         }
                     default:
                         return MODE_ALLOWED;
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 77f4093..3138639 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -216,14 +216,14 @@
      * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
      * only applies to {@link VpnService} connections.
      */
-    private boolean mAlwaysOn = false;
+    @VisibleForTesting protected boolean mAlwaysOn = false;
 
     /**
      * Whether to disable traffic outside of this VPN even when the VPN is not connected. System
      * apps can still bypass by choosing explicit networks. Has no effect if {@link mAlwaysOn} is
-     * not set.
+     * not set. Applies to all types of VPNs.
      */
-    private boolean mLockdown = false;
+    @VisibleForTesting protected boolean mLockdown = false;
 
     /**
      * Set of packages in addition to the VPN app itself that can access the network directly when
@@ -252,14 +252,14 @@
     private final int mUserHandle;
 
     public Vpn(Looper looper, Context context, INetworkManagementService netService,
-            @UserIdInt int userHandle) {
-        this(looper, context, netService, userHandle,
+            @UserIdInt int userHandle, @NonNull KeyStore keyStore) {
+        this(looper, context, netService, userHandle, keyStore,
                 new SystemServices(context), new Ikev2SessionCreator());
     }
 
     @VisibleForTesting
     protected Vpn(Looper looper, Context context, INetworkManagementService netService,
-            int userHandle, SystemServices systemServices,
+            int userHandle, @NonNull KeyStore keyStore, SystemServices systemServices,
             Ikev2SessionCreator ikev2SessionCreator) {
         mContext = context;
         mNetd = netService;
@@ -285,7 +285,7 @@
         mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
         updateCapabilities(null /* defaultNetwork */);
 
-        loadAlwaysOnPackage();
+        loadAlwaysOnPackage(keyStore);
     }
 
     /**
@@ -437,23 +437,36 @@
     /**
      * Checks if a VPN app supports always-on mode.
      *
-     * In order to support the always-on feature, an app has to
+     * <p>In order to support the always-on feature, an app has to either have an installed
+     * PlatformVpnProfile, or:
+     *
      * <ul>
-     *     <li>target {@link VERSION_CODES#N API 24} or above, and
-     *     <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
-     *         meta-data field.
+     *   <li>target {@link VERSION_CODES#N API 24} or above, and
+     *   <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
+     *       meta-data field.
      * </ul>
      *
      * @param packageName the canonical package name of the VPN app
+     * @param keyStore the keystore instance to use for checking if the app has a Platform VPN
+     *     profile installed.
      * @return {@code true} if and only if the VPN app exists and supports always-on mode
      */
-    public boolean isAlwaysOnPackageSupported(String packageName) {
+    public boolean isAlwaysOnPackageSupported(String packageName, @NonNull KeyStore keyStore) {
         enforceSettingsPermission();
 
         if (packageName == null) {
             return false;
         }
 
+        final long oldId = Binder.clearCallingIdentity();
+        try {
+            if (getVpnProfilePrivileged(packageName, keyStore) != null) {
+                return true;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(oldId);
+        }
+
         PackageManager pm = mContext.getPackageManager();
         ApplicationInfo appInfo = null;
         try {
@@ -485,27 +498,31 @@
     }
 
     /**
-     * Configures an always-on VPN connection through a specific application.
-     * This connection is automatically granted and persisted after a reboot.
+     * Configures an always-on VPN connection through a specific application. This connection is
+     * automatically granted and persisted after a reboot.
      *
-     * <p>The designated package should exist and declare a {@link VpnService} in its
-     *    manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
-     *    otherwise the call will fail.
+     * <p>The designated package should either have a PlatformVpnProfile installed, or declare a
+     * {@link VpnService} in its manifest guarded by {@link
+     * android.Manifest.permission.BIND_VPN_SERVICE}, otherwise the call will fail.
      *
      * <p>Note that this method does not check if the VPN app supports always-on mode. The check is
-     *    delayed to {@link #startAlwaysOnVpn()}, which is always called immediately after this
-     *    method in {@link android.net.IConnectivityManager#setAlwaysOnVpnPackage}.
+     * delayed to {@link #startAlwaysOnVpn()}, which is always called immediately after this method
+     * in {@link android.net.IConnectivityManager#setAlwaysOnVpnPackage}.
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
      * @param lockdownWhitelist packages to be whitelisted from lockdown.
+     * @param keyStore the Keystore instance to use for checking of PlatformVpnProfile(s)
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
     public synchronized boolean setAlwaysOnPackage(
-            String packageName, boolean lockdown, List<String> lockdownWhitelist) {
+            @Nullable String packageName,
+            boolean lockdown,
+            @Nullable List<String> lockdownWhitelist,
+            @NonNull KeyStore keyStore) {
         enforceControlPermissionOrInternalCaller();
 
-        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist)) {
+        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist, keyStore)) {
             saveAlwaysOnPackage();
             return true;
         }
@@ -513,20 +530,22 @@
     }
 
     /**
-     * Configures an always-on VPN connection through a specific application, the same as
-     * {@link #setAlwaysOnPackage}.
+     * Configures an always-on VPN connection through a specific application, the same as {@link
+     * #setAlwaysOnPackage}.
      *
-     * Does not perform permission checks. Does not persist any of the changes to storage.
+     * <p>Does not perform permission checks. Does not persist any of the changes to storage.
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
      * @param lockdownWhitelist packages to be whitelisted from lockdown. This is only used if
-     *        {@code lockdown} is {@code true}. Packages must not contain commas.
+     *     {@code lockdown} is {@code true}. Packages must not contain commas.
+     * @param keyStore the system keystore instance to check for profiles
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
     @GuardedBy("this")
     private boolean setAlwaysOnPackageInternal(
-            String packageName, boolean lockdown, List<String> lockdownWhitelist) {
+            @Nullable String packageName, boolean lockdown,
+            @Nullable List<String> lockdownWhitelist, @NonNull KeyStore keyStore) {
         if (VpnConfig.LEGACY_VPN.equals(packageName)) {
             Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
             return false;
@@ -542,11 +561,18 @@
         }
 
         if (packageName != null) {
-            // TODO: Give the minimum permission possible; if there is a Platform VPN profile, only
-            // grant ACTIVATE_PLATFORM_VPN.
-            // Pre-authorize new always-on VPN package. Grant the full ACTIVATE_VPN appop, allowing
-            // both VpnService and Platform VPNs.
-            if (!setPackageAuthorization(packageName, VpnManager.TYPE_VPN_SERVICE)) {
+            final VpnProfile profile;
+            final long oldId = Binder.clearCallingIdentity();
+            try {
+                profile = getVpnProfilePrivileged(packageName, keyStore);
+            } finally {
+                Binder.restoreCallingIdentity(oldId);
+            }
+
+            // Pre-authorize new always-on VPN package.
+            final int grantType =
+                    (profile == null) ? VpnManager.TYPE_VPN_SERVICE : VpnManager.TYPE_VPN_PLATFORM;
+            if (!setPackageAuthorization(packageName, grantType)) {
                 return false;
             }
             mAlwaysOn = true;
@@ -611,11 +637,9 @@
         }
     }
 
-    /**
-     * Load the always-on package and lockdown config from Settings.Secure
-     */
+    /** Load the always-on package and lockdown config from Settings. */
     @GuardedBy("this")
-    private void loadAlwaysOnPackage() {
+    private void loadAlwaysOnPackage(@NonNull KeyStore keyStore) {
         final long token = Binder.clearCallingIdentity();
         try {
             final String alwaysOnPackage = mSystemServices.settingsSecureGetStringForUser(
@@ -626,17 +650,21 @@
                     Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, mUserHandle);
             final List<String> whitelistedPackages = TextUtils.isEmpty(whitelistString)
                     ? Collections.emptyList() : Arrays.asList(whitelistString.split(","));
-            setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown, whitelistedPackages);
+            setAlwaysOnPackageInternal(
+                    alwaysOnPackage, alwaysOnLockdown, whitelistedPackages, keyStore);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
     /**
+     * Starts the currently selected always-on VPN
+     *
+     * @param keyStore the keyStore instance for looking up PlatformVpnProfile(s)
      * @return {@code true} if the service was started, the service was already connected, or there
-     *         was no always-on VPN to start. {@code false} otherwise.
+     *     was no always-on VPN to start. {@code false} otherwise.
      */
-    public boolean startAlwaysOnVpn() {
+    public boolean startAlwaysOnVpn(@NonNull KeyStore keyStore) {
         final String alwaysOnPackage;
         synchronized (this) {
             alwaysOnPackage = getAlwaysOnPackage();
@@ -645,8 +673,8 @@
                 return true;
             }
             // Remove always-on VPN if it's not supported.
-            if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
-                setAlwaysOnPackage(null, false, null);
+            if (!isAlwaysOnPackageSupported(alwaysOnPackage, keyStore)) {
+                setAlwaysOnPackage(null, false, null, keyStore);
                 return false;
             }
             // Skip if the service is already established. This isn't bulletproof: it's not bound
@@ -657,10 +685,24 @@
             }
         }
 
-        // Tell the OS that background services in this app need to be allowed for
-        // a short time, so we can bootstrap the VPN service.
         final long oldId = Binder.clearCallingIdentity();
         try {
+            // Prefer VPN profiles, if any exist.
+            VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage, keyStore);
+            if (profile != null) {
+                startVpnProfilePrivileged(profile, alwaysOnPackage);
+
+                // If the above startVpnProfilePrivileged() call returns, the Ikev2VpnProfile was
+                // correctly parsed, and the VPN has started running in a different thread. The only
+                // other possibility is that the above call threw an exception, which will be
+                // caught below, and returns false (clearing the always-on VPN). Once started, the
+                // Platform VPN cannot permanantly fail, and is resiliant to temporary failures. It
+                // will continue retrying until shut down by the user, or always-on is toggled off.
+                return true;
+            }
+
+            // Tell the OS that background services in this app need to be allowed for
+            // a short time, so we can bootstrap the VPN service.
             DeviceIdleInternal idleController =
                     LocalServices.getService(DeviceIdleInternal.class);
             idleController.addPowerSaveTempWhitelistApp(Process.myUid(), alwaysOnPackage,
@@ -675,6 +717,9 @@
                 Log.e(TAG, "VpnService " + serviceIntent + " failed to start", e);
                 return false;
             }
+        } catch (Exception e) {
+            Log.e(TAG, "Error starting always-on VPN", e);
+            return false;
         } finally {
             Binder.restoreCallingIdentity(oldId);
         }
@@ -2820,6 +2865,10 @@
         return isVpnProfilePreConsented(mContext, packageName);
     }
 
+    private boolean isCurrentIkev2VpnLocked(@NonNull String packageName) {
+        return isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner;
+    }
+
     /**
      * Deletes an app-provisioned VPN profile.
      *
@@ -2836,6 +2885,17 @@
 
         Binder.withCleanCallingIdentity(
                 () -> {
+                    // If this profile is providing the current VPN, turn it off, disabling
+                    // always-on as well if enabled.
+                    if (isCurrentIkev2VpnLocked(packageName)) {
+                        if (mAlwaysOn) {
+                            // Will transitively call prepareInternal(VpnConfig.LEGACY_VPN).
+                            setAlwaysOnPackage(null, false, null, keyStore);
+                        } else {
+                            prepareInternal(VpnConfig.LEGACY_VPN);
+                        }
+                    }
+
                     keyStore.delete(getProfileNameForPackage(packageName), Process.SYSTEM_UID);
                 });
     }
@@ -2946,11 +3006,9 @@
 
         // To stop the VPN profile, the caller must be the current prepared package and must be
         // running an Ikev2VpnProfile.
-        if (!isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner) {
-            return;
+        if (isCurrentIkev2VpnLocked(packageName)) {
+            prepareInternal(VpnConfig.LEGACY_VPN);
         }
-
-        prepareInternal(VpnConfig.LEGACY_VPN);
     }
 
     /**
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 6d130d9..4a1afb2 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1216,7 +1216,7 @@
         for (SyncOperation op: ops) {
             if (op.isPeriodic && op.target.matchesSpec(target)) {
                 periodicSyncs.add(new PeriodicSync(op.target.account, op.target.provider,
-                        op.extras, op.periodMillis / 1000, op.flexMillis / 1000));
+                        op.getClonedExtras(), op.periodMillis / 1000, op.flexMillis / 1000));
             }
         }
 
@@ -1478,7 +1478,7 @@
             Slog.e(TAG, "Can't schedule null sync operation.");
             return;
         }
-        if (!syncOperation.ignoreBackoff()) {
+        if (!syncOperation.hasIgnoreBackoff()) {
             Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(syncOperation.target);
             if (backoff == null) {
                 Slog.e(TAG, "Couldn't find backoff values for "
@@ -1631,7 +1631,7 @@
             getSyncStorageEngine().markPending(syncOperation.target, true);
         }
 
-        if (syncOperation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_REQUIRE_CHARGING)) {
+        if (syncOperation.hasRequireCharging()) {
             b.setRequiresCharging(true);
         }
 
@@ -1686,7 +1686,7 @@
         List<SyncOperation> ops = getAllPendingSyncs();
         for (SyncOperation op: ops) {
             if (!op.isPeriodic && op.target.matchesSpec(info)
-                    && syncExtrasEquals(extras, op.extras, false)) {
+                    && op.areExtrasEqual(extras, /*includeSyncSettings=*/ false)) {
                 cancelJob(op, "cancelScheduledSyncOperation");
             }
         }
@@ -1704,15 +1704,9 @@
             Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
         }
 
-        // The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given
-        // request. Retries of the request will always honor the backoff, so clear the
-        // flag in case we retry this request.
-        if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
-            operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
-        }
+        operation.enableBackoff();
 
-        if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)
-                && !syncResult.syncAlreadyInProgress) {
+        if (operation.hasDoNotRetry() && !syncResult.syncAlreadyInProgress) {
             // syncAlreadyInProgress flag is set by AbstractThreadedSyncAdapter. The sync adapter
             // has no way of knowing that a sync error occured. So we DO retry if the error is
             // syncAlreadyInProgress.
@@ -1720,10 +1714,9 @@
                 Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
                         + operation);
             }
-        } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)
-                && !syncResult.syncAlreadyInProgress) {
+        } else if (operation.isUpload() && !syncResult.syncAlreadyInProgress) {
             // If this was an upward sync then schedule a two-way sync immediately.
-            operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
+            operation.enableTwoWaySync();
             if (isLoggable) {
                 Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
                         + "encountered an error: " + operation);
@@ -3326,7 +3319,7 @@
             List<SyncOperation> ops = getAllPendingSyncs();
             for (SyncOperation op: ops) {
                 if (op.isPeriodic && op.target.matchesSpec(target)
-                        && syncExtrasEquals(op.extras, extras, true /* includeSyncSettings */)) {
+                        && op.areExtrasEqual(extras, /*includeSyncSettings=*/ true)) {
                     maybeUpdateSyncPeriodH(op, pollFrequencyMillis, flexMillis);
                     return;
                 }
@@ -3408,7 +3401,7 @@
             List<SyncOperation> ops = getAllPendingSyncs();
             for (SyncOperation op: ops) {
                 if (op.isPeriodic && op.target.matchesSpec(target)
-                        && syncExtrasEquals(op.extras, extras, true /* includeSyncSettings */)) {
+                        && op.areExtrasEqual(extras, /*includeSyncSettings=*/ true)) {
                     removePeriodicSyncInternalH(op, why);
                 }
             }
@@ -3559,16 +3552,18 @@
                 activeSyncContext.mIsLinkedToDeath = true;
                 syncAdapter.linkToDeath(activeSyncContext, 0);
 
-                mLogger.log("Sync start: account=" + syncOperation.target.account,
-                        " authority=", syncOperation.target.provider,
-                        " reason=", SyncOperation.reasonToString(null, syncOperation.reason),
-                        " extras=", SyncOperation.extrasToString(syncOperation.extras),
-                        " adapter=", activeSyncContext.mSyncAdapter);
+                if (mLogger.enabled()) {
+                    mLogger.log("Sync start: account=" + syncOperation.target.account,
+                            " authority=", syncOperation.target.provider,
+                            " reason=", SyncOperation.reasonToString(null, syncOperation.reason),
+                            " extras=", syncOperation.getExtrasAsString(),
+                            " adapter=", activeSyncContext.mSyncAdapter);
+                }
 
                 activeSyncContext.mSyncAdapter = ISyncAdapter.Stub.asInterface(syncAdapter);
                 activeSyncContext.mSyncAdapter
                         .startSync(activeSyncContext, syncOperation.target.provider,
-                                syncOperation.target.account, syncOperation.extras);
+                                syncOperation.target.account, syncOperation.getClonedExtras());
 
                 mLogger.log("Sync is running now...");
             } catch (RemoteException remoteExc) {
@@ -3602,9 +3597,8 @@
                         continue;
                     }
                     if (extras != null &&
-                            !syncExtrasEquals(activeSyncContext.mSyncOperation.extras,
-                                    extras,
-                                    false /* no config settings */)) {
+                            !activeSyncContext.mSyncOperation.areExtrasEqual(extras,
+                                    /*includeSyncSettings=*/ false)) {
                         continue;
                     }
                     SyncJobService.callJobFinished(activeSyncContext.mSyncOperation.jobId, false,
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
index 2abc2e6..09b7828 100644
--- a/services/core/java/com/android/server/content/SyncOperation.java
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -74,7 +74,14 @@
     /** Where this sync was initiated. */
     public final int syncSource;
     public final boolean allowParallelSyncs;
-    public final Bundle extras;
+
+    /**
+     * Sync extras. Note, DO NOT modify this bundle directly. When changing the content, always
+     * create a copy, update it, set it in this field. This is to avoid concurrent modifications
+     * when other threads are reading it.
+     */
+    private volatile Bundle mImmutableExtras;
+
     public final boolean isPeriodic;
     /** jobId of the periodic SyncOperation that initiated this one */
     public final int sourcePeriodicId;
@@ -118,20 +125,21 @@
 
     public SyncOperation(SyncOperation op, long periodMillis, long flexMillis) {
         this(op.target, op.owningUid, op.owningPackage, op.reason, op.syncSource,
-                new Bundle(op.extras), op.allowParallelSyncs, op.isPeriodic, op.sourcePeriodicId,
+                op.mImmutableExtras, op.allowParallelSyncs, op.isPeriodic, op.sourcePeriodicId,
                 periodMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE);
     }
 
     public SyncOperation(SyncStorageEngine.EndPoint info, int owningUid, String owningPackage,
-                         int reason, int source, Bundle extras, boolean allowParallelSyncs,
-                         boolean isPeriodic, int sourcePeriodicId, long periodMillis,
-                         long flexMillis, @SyncExemption int syncExemptionFlag) {
+            int reason, int source, Bundle extras,
+            boolean allowParallelSyncs,
+            boolean isPeriodic, int sourcePeriodicId, long periodMillis,
+            long flexMillis, @SyncExemption int syncExemptionFlag) {
         this.target = info;
         this.owningUid = owningUid;
         this.owningPackage = owningPackage;
         this.reason = reason;
         this.syncSource = source;
-        this.extras = new Bundle(extras);
+        this.mImmutableExtras = new Bundle(extras);
         this.allowParallelSyncs = allowParallelSyncs;
         this.isPeriodic = isPeriodic;
         this.sourcePeriodicId = sourcePeriodicId;
@@ -148,7 +156,7 @@
             return null;
         }
         SyncOperation op = new SyncOperation(target, owningUid, owningPackage, reason, syncSource,
-                new Bundle(extras), allowParallelSyncs, false, jobId /* sourcePeriodicId */,
+                mImmutableExtras, allowParallelSyncs, false, jobId /* sourcePeriodicId */,
                 periodMillis, flexMillis, ContentResolver.SYNC_EXEMPTION_NONE);
         return op;
     }
@@ -160,7 +168,10 @@
         reason = other.reason;
         syncSource = other.syncSource;
         allowParallelSyncs = other.allowParallelSyncs;
-        extras = new Bundle(other.extras);
+
+        // Since we treat this field as immutable, it's okay to use a shallow copy here.
+        // No need to create a copy.
+        mImmutableExtras = other.mImmutableExtras;
         wakeLockName = other.wakeLockName();
         isPeriodic = other.isPeriodic;
         sourcePeriodicId = other.sourcePeriodicId;
@@ -173,7 +184,8 @@
     /**
      * All fields are stored in a corresponding key in the persistable bundle.
      *
-     * {@link #extras} is a Bundle and can contain parcelable objects. But only the type Account
+     * {@link #mImmutableExtras} is a Bundle and can contain parcelable objects.
+     * But only the type Account
      * is allowed {@link ContentResolver#validateSyncExtrasBundle(Bundle)} that can't be stored in
      * a PersistableBundle. For every value of type Account with key 'key', we store a
      * PersistableBundle containing account information at key 'ACCOUNT:key'. The Account object
@@ -188,7 +200,9 @@
         PersistableBundle jobInfoExtras = new PersistableBundle();
 
         PersistableBundle syncExtrasBundle = new PersistableBundle();
-        for (String key: extras.keySet()) {
+
+        final Bundle extras = mImmutableExtras;
+        for (String key : extras.keySet()) {
             Object value = extras.get(key);
             if (value instanceof Account) {
                 Account account = (Account) value;
@@ -327,7 +341,7 @@
 
     boolean matchesPeriodicOperation(SyncOperation other) {
         return target.matchesSpec(other.target)
-                && SyncManager.syncExtrasEquals(extras, other.extras, true)
+                && SyncManager.syncExtrasEquals(mImmutableExtras, other.mImmutableExtras, true)
                 && periodMillis == other.periodMillis && flexMillis == other.flexMillis;
     }
 
@@ -345,6 +359,7 @@
     }
 
     private String toKey() {
+        final Bundle extras = mImmutableExtras;
         StringBuilder sb = new StringBuilder();
         sb.append("provider: ").append(target.provider);
         sb.append(" account {name=" + target.account.name
@@ -372,6 +387,7 @@
 
     String dump(PackageManager pm, boolean shorter, SyncAdapterStateFetcher appStates,
             boolean logSafe) {
+        final Bundle extras = mImmutableExtras;
         StringBuilder sb = new StringBuilder();
         sb.append("JobId=").append(jobId)
                 .append(" ")
@@ -468,33 +484,67 @@
     }
 
     boolean isInitialization() {
-        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
+        return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
     }
 
     boolean isExpedited() {
-        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
+        return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
     }
 
-    boolean ignoreBackoff() {
-        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
+    boolean isUpload() {
+        return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
+    }
+
+    /**
+     * Disable SYNC_EXTRAS_UPLOAD, so it will be a two-way (normal) sync.
+     */
+    void enableTwoWaySync() {
+        removeExtra(ContentResolver.SYNC_EXTRAS_UPLOAD);
+    }
+
+    boolean hasIgnoreBackoff() {
+        return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
+    }
+
+    /**
+     * Disable SYNC_EXTRAS_IGNORE_BACKOFF.
+     *
+     * The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given
+     * request. Retries of the request will always honor the backoff, so clear the
+     * flag in case we retry this request.
+     */
+    void enableBackoff() {
+        removeExtra(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
+    }
+
+    boolean hasDoNotRetry() {
+        return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false);
     }
 
     boolean isNotAllowedOnMetered() {
-        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false);
+        return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false);
     }
 
     boolean isManual() {
-        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
+        return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
     }
 
     boolean isIgnoreSettings() {
-        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
+        return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
+    }
+
+    boolean hasRequireCharging() {
+        return mImmutableExtras.getBoolean(ContentResolver.SYNC_EXTRAS_REQUIRE_CHARGING, false);
     }
 
     boolean isAppStandbyExempted() {
         return syncExemptionFlag != ContentResolver.SYNC_EXEMPTION_NONE;
     }
 
+    boolean areExtrasEqual(Bundle other, boolean includeSyncSettings) {
+        return SyncManager.syncExtrasEquals(mImmutableExtras, other, includeSyncSettings);
+    }
+
     static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
         if (bundle == null) {
             sb.append("null");
@@ -507,7 +557,7 @@
         sb.append("]");
     }
 
-    static String extrasToString(Bundle bundle) {
+    private static String extrasToString(Bundle bundle) {
         final StringBuilder sb = new StringBuilder();
         extrasToStringBuilder(bundle, sb);
         return sb.toString();
@@ -531,4 +581,25 @@
         logArray[3] = target.account.name.hashCode();
         return logArray;
     }
+
+    /**
+     * Removes a sync extra. Note do not call it from multiple threads simultaneously.
+     */
+    private void removeExtra(String key) {
+        final Bundle b = mImmutableExtras;
+        if (!b.containsKey(key)) {
+            return;
+        }
+        final Bundle clone = new Bundle(b);
+        clone.remove(key);
+        mImmutableExtras = clone;
+    }
+
+    public Bundle getClonedExtras() {
+        return new Bundle(mImmutableExtras);
+    }
+
+    public String getExtrasAsString() {
+        return extrasToString(mImmutableExtras);
+    }
 }
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index afdcda9..8c510b7 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -137,7 +137,7 @@
     /**
      * String names for the sync source types.
      *
-     * KEEP THIS AND {@link SyncStatusInfo#SOURCE_COUNT} IN SYNC.
+     * KEEP THIS AND {@link SyncStatusInfo}.SOURCE_COUNT IN SYNC.
      */
     public static final String[] SOURCES = {
             "OTHER",
@@ -1117,7 +1117,7 @@
                 Slog.v(TAG, "setActiveSync: account="
                         + " auth=" + activeSyncContext.mSyncOperation.target
                         + " src=" + activeSyncContext.mSyncOperation.syncSource
-                        + " extras=" + activeSyncContext.mSyncOperation.extras);
+                        + " extras=" + activeSyncContext.mSyncOperation.getExtrasAsString());
             }
             final EndPoint info = activeSyncContext.mSyncOperation.target;
             AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(
@@ -1179,7 +1179,7 @@
             item.eventTime = now;
             item.source = op.syncSource;
             item.reason = op.reason;
-            item.extras = op.extras;
+            item.extras = op.getClonedExtras();
             item.event = EVENT_START;
             item.syncExemptionFlag = op.syncExemptionFlag;
             mSyncHistory.add(0, item);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ad4e81b..fd86f1d 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -8609,21 +8609,11 @@
 
     @VisibleForTesting
     boolean canUseManagedServices(String pkg, Integer userId, String requiredPermission) {
-        boolean canUseManagedServices = !mActivityManager.isLowRamDevice()
-                || mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_WATCH);
-
-        for (String whitelisted : getContext().getResources().getStringArray(
-                R.array.config_allowedManagedServicesOnLowRamDevices)) {
-            if (whitelisted.equals(pkg)) {
-                canUseManagedServices = true;
-                break;
-            }
-        }
-
+        boolean canUseManagedServices = true;
         if (requiredPermission != null) {
             try {
                 if (mPackageManager.checkPermission(requiredPermission, pkg, userId)
-                    != PackageManager.PERMISSION_GRANTED) {
+                        != PackageManager.PERMISSION_GRANTED) {
                     canUseManagedServices = false;
                 }
             } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index d75eb6d..f221285 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -62,6 +62,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.content.om.OverlayConfig;
 import com.android.server.FgThread;
 import com.android.server.IoThread;
 import com.android.server.LocalServices;
@@ -248,7 +249,8 @@
             IdmapManager im = new IdmapManager(mPackageManager);
             mSettings = new OverlayManagerSettings();
             mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
-                    getDefaultOverlayPackages(), new OverlayChangeListener());
+                    OverlayConfig.getSystemInstance(), getDefaultOverlayPackages(),
+                    new OverlayChangeListener());
             mActorEnforcer = new OverlayActorEnforcer(mPackageManager);
 
             final IntentFilter packageFilter = new IntentFilter();
@@ -835,7 +837,7 @@
                     case "basecodepath":
                     case "state":
                     case "isenabled":
-                    case "isstatic":
+                    case "ismutable":
                     case "priority":
                     case "category":
                         dumpState.setField(arg);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 9623542..2493057 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -18,7 +18,7 @@
 
 import static android.content.om.OverlayInfo.STATE_DISABLED;
 import static android.content.om.OverlayInfo.STATE_ENABLED;
-import static android.content.om.OverlayInfo.STATE_ENABLED_STATIC;
+import static android.content.om.OverlayInfo.STATE_ENABLED_IMMUTABLE;
 import static android.content.om.OverlayInfo.STATE_MISSING_TARGET;
 import static android.content.om.OverlayInfo.STATE_NO_IDMAP;
 import static android.content.om.OverlayInfo.STATE_OVERLAY_IS_BEING_REPLACED;
@@ -37,6 +37,7 @@
 import android.util.ArraySet;
 import android.util.Slog;
 
+import com.android.internal.content.om.OverlayConfig;
 import com.android.internal.util.ArrayUtils;
 
 import java.io.PrintWriter;
@@ -69,6 +70,7 @@
     private final PackageManagerHelper mPackageManager;
     private final IdmapManager mIdmapManager;
     private final OverlayManagerSettings mSettings;
+    private final OverlayConfig mOverlayConfig;
     private final String[] mDefaultOverlays;
     private final OverlayChangeListener mListener;
 
@@ -83,7 +85,7 @@
      * should either scrap the overlay manager's previous settings or merge the old
      * settings with the new.
      */
-    private static boolean mustReinitializeOverlay(@NonNull final PackageInfo theTruth,
+    private boolean mustReinitializeOverlay(@NonNull final PackageInfo theTruth,
             @Nullable final OverlayInfo oldSettings) {
         if (oldSettings == null) {
             return true;
@@ -94,27 +96,35 @@
         if (!Objects.equals(theTruth.targetOverlayableName, oldSettings.targetOverlayableName)) {
             return true;
         }
-        if (theTruth.isStaticOverlayPackage() != oldSettings.isStatic) {
+
+        boolean isMutable = isPackageConfiguredMutable(theTruth.packageName);
+        if (isMutable != oldSettings.isMutable) {
             return true;
         }
-        // a change in priority is only relevant for static RROs: specifically,
-        // a regular RRO should not have its state reset only because a change
-        // in priority
-        if (theTruth.isStaticOverlayPackage()
-                && theTruth.overlayPriority != oldSettings.priority) {
+
+        if (getPackageConfiguredPriority(theTruth.packageName) != oldSettings.priority) {
             return true;
         }
+
+        // If an immutable overlay changes its configured enabled state, reinitialize the overlay.
+        if (!isMutable && isPackageConfiguredEnabled(theTruth.packageName)
+                != oldSettings.isEnabled()) {
+            return true;
+        }
+
         return false;
     }
 
     OverlayManagerServiceImpl(@NonNull final PackageManagerHelper packageManager,
             @NonNull final IdmapManager idmapManager,
             @NonNull final OverlayManagerSettings settings,
+            @NonNull final OverlayConfig overlayConfig,
             @NonNull final String[] defaultOverlays,
             @NonNull final OverlayChangeListener listener) {
         mPackageManager = packageManager;
         mIdmapManager = idmapManager;
         mSettings = settings;
+        mOverlayConfig = overlayConfig;
         mDefaultOverlays = defaultOverlays;
         mListener = listener;
     }
@@ -162,8 +172,9 @@
                         overlayPackage.overlayTarget,
                         overlayPackage.targetOverlayableName,
                         overlayPackage.applicationInfo.getBaseCodePath(),
-                        overlayPackage.isStaticOverlayPackage(),
-                        overlayPackage.overlayPriority,
+                        isPackageConfiguredMutable(overlayPackage.packageName),
+                        isPackageConfiguredEnabled(overlayPackage.packageName),
+                        getPackageConfiguredPriority(overlayPackage.packageName),
                         overlayPackage.overlayCategory);
             }
 
@@ -374,7 +385,9 @@
         mSettings.init(packageName, userId, overlayPackage.overlayTarget,
                 overlayPackage.targetOverlayableName,
                 overlayPackage.applicationInfo.getBaseCodePath(),
-                overlayPackage.isStaticOverlayPackage(), overlayPackage.overlayPriority,
+                isPackageConfiguredMutable(overlayPackage.packageName),
+                isPackageConfiguredEnabled(overlayPackage.packageName),
+                getPackageConfiguredPriority(overlayPackage.packageName),
                 overlayPackage.overlayCategory);
         try {
             if (updateState(overlayPackage.overlayTarget, packageName, userId, 0)) {
@@ -439,8 +452,10 @@
                     mListener.onOverlaysChanged(pkg.overlayTarget, userId);
                 }
                 mSettings.init(packageName, userId, pkg.overlayTarget, pkg.targetOverlayableName,
-                        pkg.applicationInfo.getBaseCodePath(), pkg.isStaticOverlayPackage(),
-                        pkg.overlayPriority, pkg.overlayCategory);
+                        pkg.applicationInfo.getBaseCodePath(),
+                        isPackageConfiguredMutable(pkg.packageName),
+                        isPackageConfiguredEnabled(pkg.packageName),
+                        getPackageConfiguredPriority(pkg.packageName), pkg.overlayCategory);
             }
 
             if (updateState(pkg.overlayTarget, packageName, userId, 0)) {
@@ -492,13 +507,13 @@
             return false;
         }
 
-        // Ignore static overlays.
-        if (overlayPackage.isStaticOverlayPackage()) {
-            return false;
-        }
-
         try {
             final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
+            if (!oi.isMutable) {
+                // Ignore immutable overlays.
+                return false;
+            }
+
             boolean modified = mSettings.setEnabled(packageName, userId, enable);
             modified |= updateState(oi.targetPackageName, oi.packageName, userId, 0);
 
@@ -534,7 +549,8 @@
             // Disable all other overlays.
             allOverlays.remove(oi);
             for (int i = 0; i < allOverlays.size(); i++) {
-                final String disabledOverlayPackageName = allOverlays.get(i).packageName;
+                final OverlayInfo disabledInfo = allOverlays.get(i);
+                final String disabledOverlayPackageName = disabledInfo.packageName;
                 final PackageInfo disabledOverlayPackageInfo = mPackageManager.getPackageInfo(
                         disabledOverlayPackageName, userId);
                 if (disabledOverlayPackageInfo == null) {
@@ -542,8 +558,8 @@
                     continue;
                 }
 
-                if (disabledOverlayPackageInfo.isStaticOverlayPackage()) {
-                    // Don't touch static overlays.
+                if (!disabledInfo.isMutable) {
+                    // Don't touch immutable overlays.
                     continue;
                 }
                 if (withinCategory && !Objects.equals(disabledOverlayPackageInfo.overlayCategory,
@@ -570,12 +586,16 @@
         }
     }
 
-    private boolean isPackageUpdatableOverlay(@NonNull final String packageName, final int userId) {
-        final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
-        if (overlayPackage == null || overlayPackage.isStaticOverlayPackage()) {
-            return false;
-        }
-        return true;
+    private boolean isPackageConfiguredMutable(@NonNull final String packageName) {
+        return mOverlayConfig.isMutable(packageName);
+    }
+
+    private int getPackageConfiguredPriority(@NonNull final String packageName) {
+        return mOverlayConfig.getPriority(packageName);
+    }
+
+    private boolean isPackageConfiguredEnabled(@NonNull final String packageName) {
+        return mOverlayConfig.isEnabled(packageName);
     }
 
     boolean setPriority(@NonNull final String packageName,
@@ -585,7 +605,7 @@
                     + newParentPackageName + " userId=" + userId);
         }
 
-        if (!isPackageUpdatableOverlay(packageName, userId)) {
+        if (!isPackageConfiguredMutable(packageName)) {
             return false;
         }
 
@@ -605,7 +625,7 @@
             Slog.d(TAG, "setHighestPriority packageName=" + packageName + " userId=" + userId);
         }
 
-        if (!isPackageUpdatableOverlay(packageName, userId)) {
+        if (!isPackageConfiguredMutable(packageName)) {
             return false;
         }
 
@@ -625,7 +645,7 @@
             Slog.d(TAG, "setLowestPriority packageName=" + packageName + " userId=" + userId);
         }
 
-        if (!isPackageUpdatableOverlay(packageName, userId)) {
+        if (!isPackageConfiguredMutable(packageName)) {
             return false;
         }
 
@@ -682,10 +702,10 @@
         final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName,
                 userId);
 
-        // Static RROs targeting to "android", ie framework-res.apk, are handled by native layers.
+        // Immutable RROs targeting to "android", ie framework-res.apk, are handled by native layers.
         if (targetPackage != null && overlayPackage != null
                 && !("android".equals(targetPackageName)
-                    && overlayPackage.isStaticOverlayPackage())) {
+                    && !isPackageConfiguredMutable(overlayPackageName))) {
             mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
         }
 
@@ -737,10 +757,6 @@
             return STATE_NO_IDMAP;
         }
 
-        if (overlayPackage.isStaticOverlayPackage()) {
-            return STATE_ENABLED_STATIC;
-        }
-
         final boolean enabled = mSettings.getEnabled(overlayPackage.packageName, userId);
         return enabled ? STATE_ENABLED : STATE_DISABLED;
     }
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index b7346d4..6bccdfc 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -67,32 +67,27 @@
     private final ArrayList<SettingsItem> mItems = new ArrayList<>();
 
     void init(@NonNull final String packageName, final int userId,
-            @NonNull final String targetPackageName,  @Nullable final String targetOverlayableName,
-            @NonNull final String baseCodePath, boolean isStatic, int priority,
+            @NonNull final String targetPackageName, @Nullable final String targetOverlayableName,
+            @NonNull final String baseCodePath, boolean isMutable, boolean isEnabled, int priority,
             @Nullable String overlayCategory) {
         remove(packageName, userId);
         final SettingsItem item =
                 new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName,
-                        baseCodePath, isStatic, priority, overlayCategory);
-        if (isStatic) {
-            // All static overlays are always enabled.
-            item.setEnabled(true);
+                        baseCodePath, OverlayInfo.STATE_UNKNOWN, isEnabled, isMutable, priority,
+                        overlayCategory);
 
-            int i;
-            for (i = mItems.size() - 1; i >= 0; i--) {
-                SettingsItem parentItem = mItems.get(i);
-                if (parentItem.mIsStatic && parentItem.mPriority <= priority) {
-                    break;
-                }
+        int i;
+        for (i = mItems.size() - 1; i >= 0; i--) {
+            SettingsItem parentItem = mItems.get(i);
+            if (parentItem.mPriority <= priority) {
+                break;
             }
-            int pos = i + 1;
-            if (pos == mItems.size()) {
-                mItems.add(item);
-            } else {
-                mItems.add(pos, item);
-            }
-        } else {
+        }
+        int pos = i + 1;
+        if (pos == mItems.size()) {
             mItems.add(item);
+        } else {
+            mItems.add(pos, item);
         }
     }
 
@@ -182,19 +177,19 @@
 
     List<OverlayInfo> getOverlaysForTarget(@NonNull final String targetPackageName,
             final int userId) {
-        // Static RROs targeting "android" are loaded from AssetManager, and so they should be
+        // Immutable RROs targeting "android" are loaded from AssetManager, and so they should be
         // ignored in OverlayManagerService.
         return selectWhereTarget(targetPackageName, userId)
-                .filter((i) -> !(i.isStatic() && "android".equals(i.getTargetPackageName())))
+                .filter((i) -> i.isMutable() || !"android".equals(i.getTargetPackageName()))
                 .map(SettingsItem::getOverlayInfo)
                 .collect(Collectors.toList());
     }
 
     ArrayMap<String, List<OverlayInfo>> getOverlaysForUser(final int userId) {
-        // Static RROs targeting "android" are loaded from AssetManager, and so they should be
+        // Immutable RROs targeting "android" are loaded from AssetManager, and so they should be
         // ignored in OverlayManagerService.
         return selectWhereUser(userId)
-                .filter((i) -> !(i.isStatic() && "android".equals(i.getTargetPackageName())))
+                .filter((i) -> i.isMutable() || !"android".equals(i.getTargetPackageName()))
                 .map(SettingsItem::getOverlayInfo)
                 .collect(Collectors.groupingBy(info -> info.targetPackageName, ArrayMap::new,
                         Collectors.toList()));
@@ -320,7 +315,7 @@
         pw.println("mBaseCodePath..........: " + item.getBaseCodePath());
         pw.println("mState.................: " + OverlayInfo.stateToString(item.getState()));
         pw.println("mIsEnabled.............: " + item.isEnabled());
-        pw.println("mIsStatic..............: " + item.isStatic());
+        pw.println("mIsMutable.............: " + item.isMutable());
         pw.println("mPriority..............: " + item.mPriority);
         pw.println("mCategory..............: " + item.mCategory);
 
@@ -352,8 +347,8 @@
             case "isenabled":
                 pw.println(item.mIsEnabled);
                 break;
-            case "isstatic":
-                pw.println(item.mIsStatic);
+            case "ismutable":
+                pw.println(item.mIsMutable);
                 break;
             case "priority":
                 pw.println(item.mPriority);
@@ -446,7 +441,7 @@
             final String category = XmlUtils.readStringAttribute(parser, ATTR_CATEGORY);
 
             return new SettingsItem(packageName, userId, targetPackageName, targetOverlayableName,
-                    baseCodePath, state, isEnabled, isStatic, priority, category);
+                    baseCodePath, state, isEnabled, !isStatic, priority, category);
         }
 
         public static void persist(@NonNull final ArrayList<SettingsItem> table,
@@ -478,7 +473,7 @@
             XmlUtils.writeStringAttribute(xml, ATTR_BASE_CODE_PATH, item.mBaseCodePath);
             XmlUtils.writeIntAttribute(xml, ATTR_STATE, item.mState);
             XmlUtils.writeBooleanAttribute(xml, ATTR_IS_ENABLED, item.mIsEnabled);
-            XmlUtils.writeBooleanAttribute(xml, ATTR_IS_STATIC, item.mIsStatic);
+            XmlUtils.writeBooleanAttribute(xml, ATTR_IS_STATIC, !item.mIsMutable);
             XmlUtils.writeIntAttribute(xml, ATTR_PRIORITY, item.mPriority);
             XmlUtils.writeStringAttribute(xml, ATTR_CATEGORY, item.mCategory);
             xml.endTag(null, TAG_ITEM);
@@ -494,36 +489,28 @@
         private @OverlayInfo.State int mState;
         private boolean mIsEnabled;
         private OverlayInfo mCache;
-        private boolean mIsStatic;
+        private boolean mIsMutable;
         private int mPriority;
         private String mCategory;
 
         SettingsItem(@NonNull final String packageName, final int userId,
                 @NonNull final String targetPackageName,
                 @Nullable final String targetOverlayableName, @NonNull final String baseCodePath,
-                final @OverlayInfo.State int state, final boolean isEnabled, final boolean isStatic,
-                final int priority,  @Nullable String category) {
+                final @OverlayInfo.State int state, final boolean isEnabled,
+                final boolean isMutable, final int priority,  @Nullable String category) {
             mPackageName = packageName;
             mUserId = userId;
             mTargetPackageName = targetPackageName;
             mTargetOverlayableName = targetOverlayableName;
             mBaseCodePath = baseCodePath;
             mState = state;
-            mIsEnabled = isEnabled || isStatic;
+            mIsEnabled = isEnabled;
             mCategory = category;
             mCache = null;
-            mIsStatic = isStatic;
+            mIsMutable = isMutable;
             mPriority = priority;
         }
 
-        SettingsItem(@NonNull final String packageName, final int userId,
-                @NonNull final String targetPackageName,
-                @Nullable final String targetOverlayableName, @NonNull final String baseCodePath,
-                final boolean isStatic, final int priority, @Nullable String category) {
-            this(packageName, userId, targetPackageName, targetOverlayableName, baseCodePath,
-                    OverlayInfo.STATE_UNKNOWN, false, isStatic, priority, category);
-        }
-
         private String getTargetPackageName() {
             return mTargetPackageName;
         }
@@ -567,7 +554,7 @@
         }
 
         private boolean setEnabled(boolean enable) {
-            if (mIsStatic) {
+            if (!mIsMutable) {
                 return false;
             }
 
@@ -591,7 +578,7 @@
         private OverlayInfo getOverlayInfo() {
             if (mCache == null) {
                 mCache = new OverlayInfo(mPackageName, mTargetPackageName, mTargetOverlayableName,
-                        mCategory, mBaseCodePath, mState, mUserId, mPriority, mIsStatic);
+                        mCategory, mBaseCodePath, mState, mUserId, mPriority, mIsMutable);
             }
             return mCache;
         }
@@ -600,8 +587,8 @@
             mCache = null;
         }
 
-        private boolean isStatic() {
-            return mIsStatic;
+        private boolean isMutable() {
+            return mIsMutable;
         }
 
         private int getPriority() {
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index eb43275..bf99bd6 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -181,7 +181,7 @@
     private void printListOverlay(PrintWriter out, OverlayInfo oi) {
         String status;
         switch (oi.state) {
-            case OverlayInfo.STATE_ENABLED_STATIC:
+            case OverlayInfo.STATE_ENABLED_IMMUTABLE:
             case OverlayInfo.STATE_ENABLED:
                 status = "[x]";
                 break;
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 0fb889c..a1250cb 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -69,7 +69,6 @@
     // Logs all filtering instead of enforcing
     private static final boolean DEBUG_ALLOW_ALL = false;
     private static final boolean DEBUG_LOGGING = false;
-    private static final boolean FEATURE_ENABLED_BY_DEFAULT = true;
 
     /**
      * This contains a list of app UIDs that are implicitly queryable because another app explicitly
@@ -135,7 +134,8 @@
     private static class FeatureConfigImpl implements FeatureConfig {
         private static final String FILTERING_ENABLED_NAME = "package_query_filtering_enabled";
         private final PackageManagerService.Injector mInjector;
-        private volatile boolean mFeatureEnabled = FEATURE_ENABLED_BY_DEFAULT;
+        private volatile boolean mFeatureEnabled =
+                PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT;
 
         private FeatureConfigImpl(PackageManagerService.Injector injector) {
             mInjector = injector;
@@ -145,14 +145,14 @@
         public void onSystemReady() {
             mFeatureEnabled = DeviceConfig.getBoolean(
                     NAMESPACE_PACKAGE_MANAGER_SERVICE, FILTERING_ENABLED_NAME,
-                    FEATURE_ENABLED_BY_DEFAULT);
+                    PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT);
             DeviceConfig.addOnPropertiesChangedListener(
                     NAMESPACE_PACKAGE_MANAGER_SERVICE, FgThread.getExecutor(),
                     properties -> {
                         if (properties.getKeyset().contains(FILTERING_ENABLED_NAME)) {
                             synchronized (FeatureConfigImpl.this) {
                                 mFeatureEnabled = properties.getBoolean(FILTERING_ENABLED_NAME,
-                                        FEATURE_ENABLED_BY_DEFAULT);
+                                        PackageManager.APP_ENUMERATION_ENABLED_BY_DEFAULT);
                             }
                         }
                     });
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index 13bd7e5..ea48395 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -32,6 +32,7 @@
 import android.content.pm.InstantAppResolveInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManagerInternal.PrivateResolveFlags;
 import android.content.pm.PackageUserState;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
@@ -229,9 +230,11 @@
     }
 
     @Nullable
-    List<ResolveInfo> queryActivities(Intent intent, String resolvedType, int flags, int userId) {
+    List<ResolveInfo> queryActivities(Intent intent, String resolvedType, int flags,
+            @PrivateResolveFlags int privateResolveFlags, int userId) {
         synchronized (mLock) {
-            return mActivities.queryIntent(intent, resolvedType, flags, userId);
+            return mActivities.queryIntent(
+                    intent, resolvedType, flags, privateResolveFlags, userId);
         }
     }
 
@@ -368,7 +371,7 @@
     @Nullable
     List<ResolveInfo> queryReceivers(Intent intent, String resolvedType, int flags, int userId) {
         synchronized (mLock) {
-            return mReceivers.queryIntent(intent, resolvedType, flags, userId);
+            return mReceivers.queryIntent(intent, resolvedType, flags, 0, userId);
         }
     }
 
@@ -1154,11 +1157,12 @@
         }
 
         List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags,
-                int userId) {
+                int privateResolveFlags, int userId) {
             if (!sUserManager.exists(userId)) {
                 return null;
             }
             mFlags = flags;
+            mPrivateResolveFlags = privateResolveFlags;
             return super.queryIntent(intent, resolvedType,
                     (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
                     userId);
@@ -1388,6 +1392,11 @@
                 }
                 return null;
             }
+            final boolean matchNonBrowserOnly =
+                    (mPrivateResolveFlags & PackageManagerInternal.RESOLVE_NON_BROWSER_ONLY) != 0;
+            if (matchNonBrowserOnly && info.handleAllWebDataURI()) {
+                return null;
+            }
             final ResolveInfo res = new ResolveInfo();
             res.activityInfo = ai;
             if ((mFlags & PackageManager.GET_RESOLVED_FILTER) != 0) {
@@ -1465,6 +1474,7 @@
         private final ArrayMap<ComponentName, ParsedActivity> mActivities =
                 new ArrayMap<>();
         private int mFlags;
+        private int mPrivateResolveFlags;
     }
 
     // Both receivers and activities share a class, but point to different get methods
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ad70345..7cfe499 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -179,12 +179,15 @@
 import android.content.pm.PackageManager.ModuleInfoFlags;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManagerInternal.PackageListObserver;
+import android.content.pm.PackageManagerInternal.PrivateResolveFlags;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.PackageParser.ParseFlags;
 import android.content.pm.PackageParser.SigningDetails;
 import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
+import android.content.pm.PackagePartitions;
+import android.content.pm.PackagePartitions.SystemPartition;
 import android.content.pm.PackageStats;
 import android.content.pm.PackageUserState;
 import android.content.pm.ParceledListSlice;
@@ -305,6 +308,7 @@
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.content.PackageHelper;
+import com.android.internal.content.om.OverlayConfig;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.os.Zygote;
@@ -792,22 +796,12 @@
      * specificity (the more generic, the earlier in the list a partition appears).
      */
     @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static final List<SystemPartition> SYSTEM_PARTITIONS = Collections.unmodifiableList(
-            Arrays.asList(
-                    new SystemPartition(Environment.getRootDirectory(), 0 /* scanFlag */,
-                            false /* hasOverlays */),
-                    new SystemPartition(Environment.getVendorDirectory(), SCAN_AS_VENDOR,
-                            true /* hasOverlays */),
-                    new SystemPartition(Environment.getOdmDirectory(), SCAN_AS_ODM,
-                            true /* hasOverlays */),
-                    new SystemPartition(Environment.getOemDirectory(), SCAN_AS_OEM,
-                            true /* hasOverlays */),
-                    new SystemPartition(Environment.getProductDirectory(), SCAN_AS_PRODUCT,
-                            true /* hasOverlays */),
-                    new SystemPartition(Environment.getSystemExtDirectory(), SCAN_AS_SYSTEM_EXT,
-                            true /* hasOverlays */)));
+    public static final List<ScanPartition> SYSTEM_PARTITIONS = Collections.unmodifiableList(
+            PackagePartitions.getOrderedPartitions(ScanPartition::new));
 
-    private final List<SystemPartition> mDirsToScanAsSystem;
+    private final List<ScanPartition> mDirsToScanAsSystem;
+
+    private final OverlayConfig mOverlayConfig;
 
     /**
      * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
@@ -2588,66 +2582,44 @@
         }
     }
 
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
-    public static class SystemPartition {
-        public final File folder;
+    @VisibleForTesting
+    public static class ScanPartition extends SystemPartition {
+        @ScanFlags
         public final int scanFlag;
-        public final File appFolder;
-        @Nullable
-        public final File privAppFolder;
-        @Nullable
-        public final File overlayFolder;
 
-
-        private static boolean shouldScanPrivApps(@ScanFlags int scanFlags) {
-            if ((scanFlags & SCAN_AS_OEM) != 0) {
-                return false;
-            }
-            if (scanFlags == 0) {  // /system partition
-                return true;
-            }
-            if ((scanFlags
-                    & (SCAN_AS_VENDOR | SCAN_AS_ODM | SCAN_AS_PRODUCT | SCAN_AS_SYSTEM_EXT)) != 0) {
-                return true;
-            }
-            if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
-                return true;
-            }
-            return false;
+        public ScanPartition(@NonNull SystemPartition partition) {
+            super(partition);
+            scanFlag = scanFlagForPartition(partition);
         }
 
-        private SystemPartition(File folder, int scanFlag, boolean hasOverlays) {
-            this.folder = folder;
-            this.scanFlag = scanFlag;
-            this.appFolder = toCanonical(new File(folder, "app"));
-            this.privAppFolder = shouldScanPrivApps(scanFlag)
-                    ? toCanonical(new File(folder, "priv-app"))
-                    : null;
-            this.overlayFolder = hasOverlays ? toCanonical(new File(folder, "overlay")) : null;
+        /**
+         * Creates a partition containing the same folders as the original partition but with a
+         * different root folder. The new partition will include the scan flags of the original
+         * partition along with any specified additional scan flags.
+         */
+        public ScanPartition(@NonNull File folder, @NonNull ScanPartition original,
+                @ScanFlags int additionalScanFlag) {
+            super(folder, original);
+            this.scanFlag = original.scanFlag | additionalScanFlag;
         }
 
-        public boolean containsPrivApp(File scanFile) {
-            return FileUtils.contains(privAppFolder, scanFile);
-        }
-
-        public boolean containsApp(File scanFile) {
-            return FileUtils.contains(appFolder, scanFile);
-        }
-
-        public boolean containsPath(String path) {
-            return path.startsWith(folder.getPath() + "/");
-        }
-
-        public boolean containsPrivPath(String path) {
-            return privAppFolder != null && path.startsWith(privAppFolder.getPath() + "/");
-        }
-
-        private static File toCanonical(File dir) {
-            try {
-                return dir.getCanonicalFile();
-            } catch (IOException e) {
-                // failed to look up canonical path, continue with original one
-                return dir;
+        private static int scanFlagForPartition(PackagePartitions.SystemPartition partition) {
+            switch (partition.type) {
+                case PackagePartitions.PARTITION_SYSTEM:
+                    return 0;
+                case PackagePartitions.PARTITION_VENDOR:
+                    return SCAN_AS_VENDOR;
+                case PackagePartitions.PARTITION_ODM:
+                    return SCAN_AS_ODM;
+                case PackagePartitions.PARTITION_OEM:
+                    return SCAN_AS_OEM;
+                case PackagePartitions.PARTITION_PRODUCT:
+                    return SCAN_AS_PRODUCT;
+                case PackagePartitions.PARTITION_SYSTEM_EXT:
+                    return SCAN_AS_SYSTEM_EXT;
+                default:
+                    throw new IllegalStateException("Unable to determine scan flag for "
+                            + partition.folder);
             }
         }
     }
@@ -2751,7 +2723,7 @@
         mDirsToScanAsSystem = new ArrayList<>();
         mDirsToScanAsSystem.addAll(SYSTEM_PARTITIONS);
         mDirsToScanAsSystem.addAll(mApexManager.getActiveApexInfos().stream()
-                .map(ai -> resolveApexToSystemPartition(ai))
+                .map(PackageManagerService::resolveApexToScanPartition)
                 .filter(Objects::nonNull).collect(Collectors.toList()));
         Slog.d(TAG,
                 "Directories scanned as system partitions: [" + mDirsToScanAsSystem.stream().map(
@@ -2900,11 +2872,11 @@
             // For security and version matching reason, only consider overlay packages if they
             // reside in the right directory.
             for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
-                final SystemPartition partition = mDirsToScanAsSystem.get(i);
-                if (partition.overlayFolder == null) {
+                final ScanPartition partition = mDirsToScanAsSystem.get(i);
+                if (partition.getOverlayFolder() == null) {
                     continue;
                 }
-                scanDirTracedLI(partition.overlayFolder, systemParseFlags,
+                scanDirTracedLI(partition.getOverlayFolder(), systemParseFlags,
                         systemScanFlags | partition.scanFlag, 0,
                         packageParser, executorService);
             }
@@ -2917,17 +2889,20 @@
                         "Failed to load frameworks package; check log for warnings");
             }
             for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
-                final SystemPartition partition = mDirsToScanAsSystem.get(i);
-                if (partition.privAppFolder != null) {
-                    scanDirTracedLI(partition.privAppFolder, systemParseFlags,
+                final ScanPartition partition = mDirsToScanAsSystem.get(i);
+                if (partition.getPrivAppFolder() != null) {
+                    scanDirTracedLI(partition.getPrivAppFolder(), systemParseFlags,
                             systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0,
                             packageParser, executorService);
                 }
-                scanDirTracedLI(partition.appFolder, systemParseFlags,
+                scanDirTracedLI(partition.getAppFolder(), systemParseFlags,
                         systemScanFlags | partition.scanFlag, 0,
                         packageParser, executorService);
             }
 
+            // Parse overlay configuration files to set default enable state, mutability, and
+            // priority of system overlays.
+            mOverlayConfig = OverlayConfig.initializeSystemInstance(mPmInternal::forEachPackage);
 
             // Prune any system packages that no longer exist.
             final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
@@ -3105,7 +3080,7 @@
                         @ParseFlags int reparseFlags = 0;
                         @ScanFlags int rescanFlags = 0;
                         for (int i1 = 0, size = mDirsToScanAsSystem.size(); i1 < size; i1++) {
-                            SystemPartition partition = mDirsToScanAsSystem.get(i1);
+                            final ScanPartition partition = mDirsToScanAsSystem.get(i1);
                             if (partition.containsPrivApp(scanFile)) {
                                 reparseFlags = systemParseFlags;
                                 rescanFlags = systemScanFlags | SCAN_AS_PRIVILEGED
@@ -6128,8 +6103,8 @@
     @Override
     public ResolveInfo resolveIntent(Intent intent, String resolvedType,
             int flags, int userId) {
-        return resolveIntentInternal(intent, resolvedType, flags, userId, false,
-                Binder.getCallingUid());
+        return resolveIntentInternal(intent, resolvedType, flags, 0 /*privateResolveFlags*/,
+                userId, false, Binder.getCallingUid());
     }
 
     /**
@@ -6137,8 +6112,9 @@
      * However, if {@code resolveForStart} is {@code true}, all instant apps are visible
      * since we need to allow the system to start any installed application.
      */
-    private ResolveInfo resolveIntentInternal(Intent intent, String resolvedType,
-            int flags, int userId, boolean resolveForStart, int filterCallingUid) {
+    private ResolveInfo resolveIntentInternal(Intent intent, String resolvedType, int flags,
+            @PrivateResolveFlags int privateResolveFlags, int userId, boolean resolveForStart,
+            int filterCallingUid) {
         try {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent");
 
@@ -6150,11 +6126,13 @@
 
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
             final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType,
-                    flags, filterCallingUid, userId, resolveForStart, true /*allowDynamicSplits*/);
+                    flags, privateResolveFlags, filterCallingUid, userId, resolveForStart,
+                    true /*allowDynamicSplits*/);
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
             final ResolveInfo bestChoice =
-                    chooseBestActivity(intent, resolvedType, flags, query, userId);
+                    chooseBestActivity(
+                            intent, resolvedType, flags, privateResolveFlags, query, userId);
             return bestChoice;
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -6312,7 +6290,7 @@
     }
 
     private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
-            int flags, List<ResolveInfo> query, int userId) {
+            int flags, int privateResolveFlags, List<ResolveInfo> query, int userId) {
         if (query != null) {
             final int N = query.size();
             if (N == 1) {
@@ -6354,6 +6332,10 @@
                         }
                     }
                 }
+                if ((privateResolveFlags
+                        & PackageManagerInternal.RESOLVE_NON_RESOLVER_ONLY) != 0) {
+                    return null;
+                }
                 ri = new ResolveInfo(mResolveInfo);
                 ri.activityInfo = new ActivityInfo(ri.activityInfo);
                 ri.activityInfo.labelRes = ResolverActivity.getLabelRes(intent.getAction());
@@ -6767,13 +6749,13 @@
     private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
             String resolvedType, int flags, int userId) {
         return queryIntentActivitiesInternal(
-                intent, resolvedType, flags, Binder.getCallingUid(), userId,
-                false /*resolveForStart*/, true /*allowDynamicSplits*/);
+                intent, resolvedType, flags, 0 /*privateResolveFlags*/, Binder.getCallingUid(),
+                userId, false /*resolveForStart*/, true /*allowDynamicSplits*/);
     }
 
     private @NonNull List<ResolveInfo> queryIntentActivitiesInternal(Intent intent,
-            String resolvedType, int flags, int filterCallingUid, int userId,
-            boolean resolveForStart, boolean allowDynamicSplits) {
+            String resolvedType, int flags, @PrivateResolveFlags int privateResolveFlags,
+            int filterCallingUid, int userId, boolean resolveForStart, boolean allowDynamicSplits) {
         if (!mUserManager.exists(userId)) return Collections.emptyList();
         final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
         mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
@@ -6858,7 +6840,7 @@
 
                 // Check for results in the current profile.
                 result = filterIfNotSystemUser(mComponentResolver.queryActivities(
-                        intent, resolvedType, flags, userId), userId);
+                        intent, resolvedType, flags, privateResolveFlags, userId), userId);
                 addInstant = isInstantAppResolutionAllowed(intent, result, userId,
                         false /*skipPackageCheck*/);
                 // Check for cross profile results.
@@ -6957,7 +6939,7 @@
                         | PackageManager.GET_RESOLVED_FILTER
                         | PackageManager.MATCH_INSTANT
                         | PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY,
-                    userId);
+                    0, userId);
             for (int i = instantApps.size() - 1; i >= 0; --i) {
                 final ResolveInfo info = instantApps.get(i);
                 final String packageName = info.activityInfo.packageName;
@@ -7061,7 +7043,7 @@
             return null;
         }
         List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(intent,
-                resolvedType, flags, parentUserId);
+                resolvedType, flags, 0, parentUserId);
 
         if (resultTargetUser == null || resultTargetUser.isEmpty()) {
             return null;
@@ -7246,8 +7228,9 @@
         failureActivityIntent.setPackage(packageName);
         // IMPORTANT: disallow dynamic splits to avoid an infinite loop
         final List<ResolveInfo> result = queryIntentActivitiesInternal(
-                failureActivityIntent, null /*resolvedType*/, 0 /*flags*/, filterCallingUid, userId,
-                false /*resolveForStart*/, false /*allowDynamicSplits*/);
+                failureActivityIntent, null /*resolvedType*/, 0 /*flags*/,
+                0 /*privateResolveFlags*/, filterCallingUid, userId, false /*resolveForStart*/,
+                false /*allowDynamicSplits*/);
         final int NR = result.size();
         if (NR > 0) {
             for (int i = 0; i < NR; i++) {
@@ -7502,7 +7485,7 @@
             String resolvedType, int flags, int sourceUserId) {
         int targetUserId = filter.getTargetUserId();
         List<ResolveInfo> resultTargetUser = mComponentResolver.queryActivities(intent,
-                resolvedType, flags, targetUserId);
+                resolvedType, flags, 0, targetUserId);
         if (resultTargetUser != null && isUserEnabled(targetUserId)) {
             // If all the matches in the target profile are suspended, return null.
             for (int i = resultTargetUser.size() - 1; i >= 0; i--) {
@@ -11570,50 +11553,17 @@
                     // We are scanning a system overlay. This can be the first scan of the
                     // system/vendor/oem partition, or an update to the system overlay.
                     if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
-                        // This must be an update to a system overlay.
-                        final PackageSetting previousPkg = assertNotNull(
-                                mSettings.getPackageLPr(pkg.getPackageName()),
-                                "previous package state not present");
-
-                        // previousPkg.pkg may be null: the package will be not be scanned if the
-                        // package manager knows there is a newer version on /data.
-                        // TODO[b/79435695]: Find a better way to keep track of the "static"
-                        // property for RROs instead of having to parse packages on /system
-                        AndroidPackage ppkg = previousPkg.pkg;
-                        if (ppkg == null) {
-                            try {
-                                final PackageParser pp = new PackageParser();
-                                // TODO(b/135203078): Do we really need to parse here? Maybe use
-                                //  a shortened path?
-                                ppkg = pp.parseParsedPackage(previousPkg.codePath,
-                                        parseFlags | PackageParser.PARSE_IS_SYSTEM_DIR,
-                                        false)
-                                        .hideAsFinal();
-                            } catch (PackageParserException e) {
-                                Slog.w(TAG, "failed to parse " + previousPkg.codePath, e);
-                            }
-                        }
-
-                        // Static overlays cannot be updated.
-                        if (ppkg != null && ppkg.isOverlayIsStatic()) {
+                        // This must be an update to a system overlay. Immutable overlays cannot be
+                        // upgraded.
+                        Objects.requireNonNull(mOverlayConfig,
+                                "Parsing non-system dir before overlay configs are initialized");
+                        if (!mOverlayConfig.isMutable(pkg.getPackageName())) {
                             throw new PackageManagerException("Overlay "
                                     + pkg.getPackageName()
                                     + " is static and cannot be upgraded.");
-                        // Non-static overlays cannot be converted to static overlays.
-                        } else if (pkg.isOverlayIsStatic()) {
-                            throw new PackageManagerException("Overlay "
-                                    + pkg.getPackageName()
-                                    + " cannot be upgraded into a static overlay.");
                         }
                     }
                 } else {
-                    // The overlay is a non-system overlay. Non-system overlays cannot be static.
-                    if (pkg.isOverlayIsStatic()) {
-                        throw new PackageManagerException("Overlay "
-                                + pkg.getPackageName()
-                                + " is static but not pre-installed.");
-                    }
-
                     // A non-preloaded overlay packages must have targetSdkVersion >= Q, or be
                     // signed with the platform certificate. Check this in increasing order of
                     // computational cost.
@@ -17971,14 +17921,13 @@
         }
     }
 
-    private static @Nullable SystemPartition resolveApexToSystemPartition(
+    private static @Nullable ScanPartition resolveApexToScanPartition(
             ApexManager.ActiveApexInfo apexInfo) {
         for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
-            SystemPartition sp = SYSTEM_PARTITIONS.get(i);
+            ScanPartition sp = SYSTEM_PARTITIONS.get(i);
             if (apexInfo.preInstalledApexPath.getAbsolutePath().startsWith(
                     sp.folder.getAbsolutePath())) {
-                return new SystemPartition(apexInfo.apexDirectory,
-                        sp.scanFlag | SCAN_AS_APK_IN_APEX, false /* hasOverlays */);
+                return new ScanPartition(apexInfo.apexDirectory, sp, SCAN_AS_APK_IN_APEX);
             }
         }
         return null;
@@ -18079,7 +18028,7 @@
                 | PackageParser.PARSE_IS_SYSTEM_DIR;
         @ScanFlags int scanFlags = SCAN_AS_SYSTEM;
         for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
-            SystemPartition partition = mDirsToScanAsSystem.get(i);
+            ScanPartition partition = mDirsToScanAsSystem.get(i);
             if (partition.containsPath(codePathString)) {
                 scanFlags |= partition.scanFlag;
                 if (partition.containsPrivPath(codePathString)) {
@@ -23324,7 +23273,7 @@
         public List<ResolveInfo> queryIntentActivities(
                 Intent intent, String resolvedType, int flags, int filterCallingUid, int userId) {
             return PackageManagerService.this
-                    .queryIntentActivitiesInternal(intent, resolvedType, flags, filterCallingUid,
+                    .queryIntentActivitiesInternal(intent, resolvedType, flags, 0, filterCallingUid,
                             userId, false /*resolveForStart*/, true /*allowDynamicSplits*/);
         }
 
@@ -23610,9 +23559,11 @@
 
         @Override
         public ResolveInfo resolveIntent(Intent intent, String resolvedType,
-                int flags, int userId, boolean resolveForStart, int filterCallingUid) {
+                int flags, int privateResolveFlags, int userId, boolean resolveForStart,
+                int filterCallingUid) {
             return resolveIntentInternal(
-                    intent, resolvedType, flags, userId, resolveForStart, filterCallingUid);
+                    intent, resolvedType, flags, privateResolveFlags, userId, resolveForStart,
+                    filterCallingUid);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 81c7471..97f9548 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -13,6 +13,9 @@
       "name": "AppEnumerationTests"
     },
     {
+      "name": "CtsMatchFlagTestCases"
+    },
+    {
       "name": "FrameworksServicesTests",
       "options": [
         {
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index c13cb38..ec8e1a0 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -39,7 +39,9 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManagerInternal.PackageListObserver;
 import android.content.pm.PermissionInfo;
+import android.content.pm.parsing.AndroidPackage;
 import android.os.Build;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
@@ -50,15 +52,14 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LongSparseLongArray;
+import android.util.Pair;
 import android.util.Slog;
-import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.infra.AndroidFuture;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IntPair;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.FgThread;
@@ -69,7 +70,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Objects;
 import java.util.concurrent.ExecutionException;
 
 /**
@@ -100,7 +100,7 @@
      * scheduled for a package/user.
      */
     @GuardedBy("mLock")
-    private final ArraySet<Integer> mIsPackageSyncsScheduled = new ArraySet<>();
+    private final ArraySet<Pair<String, Integer>> mIsPackageSyncsScheduled = new ArraySet<>();
 
     public PermissionPolicyService(@NonNull Context context) {
         super(context);
@@ -125,8 +125,10 @@
 
             @Override
             public void onPackageChanged(String packageName, int uid) {
-                if (isStarted(UserHandle.getUserId(uid))) {
-                    synchronizePackagePermissionsAndAppOpsForUser(uid);
+                final int userId = UserHandle.getUserId(uid);
+
+                if (isStarted(userId)) {
+                    synchronizePackagePermissionsAndAppOpsForUser(packageName, userId);
                 }
             }
 
@@ -137,21 +139,12 @@
         });
 
         permManagerInternal.addOnRuntimePermissionStateChangedListener(
-                (packageName, userId) -> {
-                    int uid;
-                    try {
-                        uid = getContext().getPackageManager().getPackageUidAsUser(packageName, 0,
-                                userId);
-                    } catch (NameNotFoundException e) {
-                        Slog.e(LOG_TAG, "Cannot synchronize changed package " + packageName, e);
-                        return;
-                    }
-                    synchronizeUidPermissionsAndAppOpsAsync(uid);
-                });
+                this::synchronizePackagePermissionsAndAppOpsAsyncForUser);
 
         mAppOpsCallback = new IAppOpsCallback.Stub() {
             public void opChanged(int op, int uid, String packageName) {
-                synchronizeUidPermissionsAndAppOpsAsync(uid);
+                synchronizePackagePermissionsAndAppOpsAsyncForUser(packageName,
+                        UserHandle.getUserId(uid));
             }
         };
 
@@ -201,17 +194,19 @@
         return AppOpsManager.opToSwitch(op);
     }
 
-    private void synchronizeUidPermissionsAndAppOpsAsync(int uid) {
-        if (isStarted(UserHandle.getUserId(uid))) {
+    private void synchronizePackagePermissionsAndAppOpsAsyncForUser(@NonNull String packageName,
+            @UserIdInt int changedUserId) {
+        if (isStarted(changedUserId)) {
             synchronized (mLock) {
-                if (mIsPackageSyncsScheduled.add(uid)) {
+                if (mIsPackageSyncsScheduled.add(new Pair<>(packageName, changedUserId))) {
                     FgThread.getHandler().sendMessage(PooledLambda.obtainMessage(
                             PermissionPolicyService
                                     ::synchronizePackagePermissionsAndAppOpsForUser,
-                            this, uid));
+                            this, packageName, changedUserId));
                 } else {
                     if (DEBUG) {
-                        Slog.v(LOG_TAG, "sync for " + uid + " already scheduled");
+                        Slog.v(LOG_TAG, "sync for " + packageName + "/" + changedUserId
+                                + " already scheduled");
                     }
                 }
             }
@@ -340,20 +335,39 @@
     /**
      * Synchronize a single package.
      */
-    private void synchronizePackagePermissionsAndAppOpsForUser(int uid) {
+    private void synchronizePackagePermissionsAndAppOpsForUser(@NonNull String packageName,
+            @UserIdInt int userId) {
         synchronized (mLock) {
-            mIsPackageSyncsScheduled.remove(uid);
+            mIsPackageSyncsScheduled.remove(new Pair<>(packageName, userId));
         }
 
         if (DEBUG) {
             Slog.v(LOG_TAG,
-                    "synchronizePackagePermissionsAndAppOpsForUser(" + uid + ")");
+                    "synchronizePackagePermissionsAndAppOpsForUser(" + packageName + ", "
+                            + userId + ")");
         }
 
+        final PackageManagerInternal packageManagerInternal = LocalServices.getService(
+                PackageManagerInternal.class);
+        final PackageInfo pkg = packageManagerInternal.getPackageInfo(packageName, 0,
+                Process.SYSTEM_UID, userId);
+        if (pkg == null) {
+            return;
+        }
         final PermissionToOpSynchroniser synchroniser = new PermissionToOpSynchroniser(
-                getUserContext(getContext(), UserHandle.getUserHandleForUid(uid)));
-        synchroniser.addUid(uid);
-        synchroniser.syncUids();
+                getUserContext(getContext(), UserHandle.of(userId)));
+        synchroniser.addPackage(pkg.packageName);
+        final String[] sharedPkgNames = packageManagerInternal.getSharedUserPackagesForPackage(
+                pkg.packageName, userId);
+
+        for (String sharedPkgName : sharedPkgNames) {
+            final AndroidPackage sharedPkg = packageManagerInternal
+                    .getPackage(sharedPkgName);
+            if (sharedPkg != null) {
+                synchroniser.addPackage(sharedPkg.getPackageName());
+            }
+        }
+        synchroniser.syncPackages();
     }
 
     /**
@@ -367,8 +381,8 @@
         final PermissionToOpSynchroniser synchronizer = new PermissionToOpSynchroniser(
                 getUserContext(getContext(), UserHandle.of(userId)));
         packageManagerInternal.forEachPackage(
-                (pkg) -> synchronizer.addUid(pkg.getUid()));
-        synchronizer.syncUids();
+                (pkg) -> synchronizer.addPackage(pkg.getPackageName()));
+        synchronizer.syncPackages();
     }
 
     /**
@@ -383,51 +397,37 @@
 
         private final @NonNull ArrayMap<String, PermissionInfo> mRuntimePermissionInfos;
 
-        // Cache uid -> packageNames
-        private SparseArray<String[]> mUidToPkg = new SparseArray<>();
-
         /**
          * All ops that need to be flipped to allow.
          *
-         * @see #syncUids
+         * @see #syncPackages
          */
-        private final @NonNull ArraySet<OpToChange> mOpsToAllow = new ArraySet<>();
+        private final @NonNull ArrayList<OpToChange> mOpsToAllow = new ArrayList<>();
 
         /**
          * All ops that need to be flipped to ignore.
          *
-         * @see #syncUids
+         * @see #syncPackages
          */
-        private final @NonNull ArraySet<OpToChange> mOpsToIgnore = new ArraySet<>();
+        private final @NonNull ArrayList<OpToChange> mOpsToIgnore = new ArrayList<>();
 
         /**
          * All ops that need to be flipped to ignore if not allowed.
          *
          * Currently, only used by soft restricted permissions logic.
          *
-         * @see #syncUids
+         * @see #syncPackages
          */
-        private final @NonNull ArraySet<OpToChange> mOpsToIgnoreIfNotAllowed = new ArraySet<>();
+        private final @NonNull ArrayList<OpToChange> mOpsToIgnoreIfNotAllowed = new ArrayList<>();
 
         /**
          * All ops that need to be flipped to foreground.
          *
          * Currently, only used by the foreground/background permissions logic.
          *
-         * @see #syncUids
+         * @see #syncPackages
          */
-        private final @NonNull ArraySet<OpToChange> mOpsToForeground = new ArraySet<>();
-
-        private @Nullable String[] getPackageNamesForUid(int uid) {
-            String[] pkgs = mUidToPkg.get(uid);
-            if (pkgs != null) {
-                return pkgs;
-            }
-
-            pkgs = mPackageManager.getPackagesForUid(uid);
-            mUidToPkg.put(uid, pkgs);
-            return pkgs;
-        }
+        private final @NonNull ArrayList<OpToChange> mOpsToForeground = new ArrayList<>();
 
         PermissionToOpSynchroniser(@NonNull Context context) {
             mContext = context;
@@ -449,11 +449,11 @@
         }
 
         /**
-         * Set app ops that were added in {@link #addUid}.
+         * Set app ops that were added in {@link #addPackage}.
          *
          * <p>This processes ops previously added by {@link #addAppOps(PackageInfo, String)}
          */
-        private void syncUids() {
+        private void syncPackages() {
             // Remember which ops were already set. This makes sure that we always set the most
             // permissive mode if two OpChanges are scheduled. This can e.g. happen if two
             // permissions change the same op. See {@link #getSwitchOp}.
@@ -461,42 +461,42 @@
 
             final int allowCount = mOpsToAllow.size();
             for (int i = 0; i < allowCount; i++) {
-                final OpToChange op = mOpsToAllow.valueAt(i);
+                final OpToChange op = mOpsToAllow.get(i);
 
-                setUidModeAllowed(op.code, op.uid);
+                setUidModeAllowed(op.code, op.uid, op.packageName);
                 alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
             }
 
             final int foregroundCount = mOpsToForeground.size();
             for (int i = 0; i < foregroundCount; i++) {
-                final OpToChange op = mOpsToForeground.valueAt(i);
+                final OpToChange op = mOpsToForeground.get(i);
                 if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
                     continue;
                 }
 
-                setUidModeForeground(op.code, op.uid);
+                setUidModeForeground(op.code, op.uid, op.packageName);
                 alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
             }
 
             final int ignoreCount = mOpsToIgnore.size();
             for (int i = 0; i < ignoreCount; i++) {
-                final OpToChange op = mOpsToIgnore.valueAt(i);
+                final OpToChange op = mOpsToIgnore.get(i);
                 if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
                     continue;
                 }
 
-                setUidModeIgnored(op.code, op.uid);
+                setUidModeIgnored(op.code, op.uid, op.packageName);
                 alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
             }
 
             final int ignoreIfNotAllowedCount = mOpsToIgnoreIfNotAllowed.size();
             for (int i = 0; i < ignoreIfNotAllowedCount; i++) {
-                final OpToChange op = mOpsToIgnoreIfNotAllowed.valueAt(i);
+                final OpToChange op = mOpsToIgnoreIfNotAllowed.get(i);
                 if (alreadySetAppOps.indexOfKey(IntPair.of(op.uid, op.code)) >= 0) {
                     continue;
                 }
 
-                boolean wasSet = setUidModeIgnoredIfNotAllowed(op.code, op.uid);
+                boolean wasSet = setUidModeIgnoredIfNotAllowed(op.code, op.uid, op.packageName);
                 if (wasSet) {
                     alreadySetAppOps.put(IntPair.of(op.uid, op.code), 1);
                 }
@@ -555,7 +555,7 @@
             }
 
             int uid = packageInfo.applicationInfo.uid;
-            OpToChange opToChange = new OpToChange(uid, appOpCode);
+            OpToChange opToChange = new OpToChange(uid, packageName, appOpCode);
             switch (appOpMode) {
                 case MODE_ALLOWED:
                     mOpsToAllow.add(opToChange);
@@ -618,7 +618,8 @@
             }
 
             int uid = packageInfo.applicationInfo.uid;
-            OpToChange extraOpToChange = new OpToChange(uid, extraOpCode);
+            String packageName = packageInfo.packageName;
+            OpToChange extraOpToChange = new OpToChange(uid, packageName, extraOpCode);
             if (policy.mayAllowExtraAppOp()) {
                 mOpsToAllow.add(extraOpToChange);
             } else {
@@ -631,56 +632,45 @@
         }
 
         /**
-         * Add a Uid for {@link #syncUids() processing} later.
+         * Add a package for {@link #syncPackages() processing} later.
          *
          * <p>Note: Called with the package lock held. Do <u>not</u> call into app-op manager.
          *
-         * @param uid The uid to add for later processing.
+         * @param pkgName The package to add for later processing.
          */
-        void addUid(int uid) {
-            String[] pkgNames = getPackageNamesForUid(uid);
-            if (pkgNames == null) {
+        void addPackage(@NonNull String pkgName) {
+            final PackageInfo pkg;
+            try {
+                pkg = mPackageManager.getPackageInfo(pkgName, GET_PERMISSIONS);
+            } catch (NameNotFoundException e) {
                 return;
             }
 
-            for (String pkgName : pkgNames) {
-                final PackageInfo pkg;
-                try {
-                    pkg = mPackageManager.getPackageInfo(pkgName, GET_PERMISSIONS);
-                } catch (NameNotFoundException e) {
-                    continue;
-                }
+            if (pkg.requestedPermissions == null) {
+                return;
+            }
 
-                if (pkg.requestedPermissions == null) {
-                    continue;
-                }
-
-                for (String permission : pkg.requestedPermissions) {
-                    addAppOps(pkg, permission);
-                }
+            for (String permission : pkg.requestedPermissions) {
+                addAppOps(pkg, permission);
             }
         }
 
-        private void setUidModeAllowed(int opCode, int uid) {
-            setUidMode(opCode, uid, MODE_ALLOWED);
+        private void setUidModeAllowed(int opCode, int uid, @NonNull String packageName) {
+            setUidMode(opCode, uid, MODE_ALLOWED, packageName);
         }
 
-        private void setUidModeForeground(int opCode, int uid) {
-            setUidMode(opCode, uid, MODE_FOREGROUND);
+        private void setUidModeForeground(int opCode, int uid, @NonNull String packageName) {
+            setUidMode(opCode, uid, MODE_FOREGROUND, packageName);
         }
 
-        private void setUidModeIgnored(int opCode, int uid) {
-            setUidMode(opCode, uid, MODE_IGNORED);
+        private void setUidModeIgnored(int opCode, int uid, @NonNull String packageName) {
+            setUidMode(opCode, uid, MODE_IGNORED, packageName);
         }
 
-        private boolean setUidModeIgnoredIfNotAllowed(int opCode, int uid) {
-            String[] pkgsOfUid = getPackageNamesForUid(uid);
-            if (ArrayUtils.isEmpty(pkgsOfUid)) {
-                return false;
-            }
-
+        private boolean setUidModeIgnoredIfNotAllowed(int opCode, int uid,
+                @NonNull String packageName) {
             final int currentMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
-                    opCode), uid, pkgsOfUid[0]);
+                    opCode), uid, packageName);
             if (currentMode != MODE_ALLOWED) {
                 if (currentMode != MODE_IGNORED) {
                     mAppOpsManagerInternal.setUidModeFromPermissionPolicy(opCode, uid, MODE_IGNORED,
@@ -691,24 +681,20 @@
             return false;
         }
 
-        private void setUidMode(int opCode, int uid, int mode) {
-            String[] pkgsOfUid = getPackageNamesForUid(uid);
-            if (ArrayUtils.isEmpty(pkgsOfUid)) {
-                return;
-            }
-
+        private void setUidMode(int opCode, int uid, int mode,
+                @NonNull String packageName) {
             final int oldMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
-                    opCode), uid, pkgsOfUid[0]);
+                    opCode), uid, packageName);
             if (oldMode != mode) {
                 mAppOpsManagerInternal.setUidModeFromPermissionPolicy(opCode, uid, mode,
                         mAppOpsCallback);
                 final int newMode = mAppOpsManager.unsafeCheckOpRaw(AppOpsManager.opToPublicName(
-                        opCode), uid, pkgsOfUid[0]);
+                        opCode), uid, packageName);
                 if (newMode != mode) {
                     // Work around incorrectly-set package mode. It never makes sense for app ops
                     // related to runtime permissions, but can get in the way and we have to reset
                     // it.
-                    mAppOpsManagerInternal.setModeFromPermissionPolicy(opCode, uid, pkgsOfUid[0],
+                    mAppOpsManagerInternal.setModeFromPermissionPolicy(opCode, uid, packageName,
                             AppOpsManager.opToDefaultMode(opCode), mAppOpsCallback);
                 }
             }
@@ -716,30 +702,14 @@
 
         private class OpToChange {
             final int uid;
+            final @NonNull String packageName;
             final int code;
 
-            OpToChange(int uid, int code) {
+            OpToChange(int uid, @NonNull String packageName, int code) {
                 this.uid = uid;
+                this.packageName = packageName;
                 this.code = code;
             }
-
-            @Override
-            public boolean equals(Object o) {
-                if (this == o) {
-                    return true;
-                }
-                if (o == null || getClass() != o.getClass()) {
-                    return false;
-                }
-
-                OpToChange other = (OpToChange) o;
-                return uid == other.uid && code == other.code;
-            }
-
-            @Override
-            public int hashCode() {
-                return Objects.hash(uid, code);
-            }
         }
     }
 
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index 4b3746b..42aaec9 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -176,8 +176,10 @@
     @GuardedBy("mLock")
     private int mSettingBatterySaverStickyAutoDisableThreshold;
 
-    /** Config flag to track default disable threshold for Dynamic Power Savings enabled battery
-     * saver. */
+    /**
+     * Config flag to track default disable threshold for Dynamic Power Savings enabled battery
+     * saver.
+     */
     @GuardedBy("mLock")
     private final int mDynamicPowerSavingsDefaultDisableThreshold;
 
@@ -192,8 +194,9 @@
     @GuardedBy("mLock")
     private int mSettingAutomaticBatterySaver;
 
-    /** When to disable battery saver again if it was enabled due to an external suggestion.
-     *  Corresponds to Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD.
+    /**
+     * When to disable battery saver again if it was enabled due to an external suggestion.
+     * Corresponds to Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD.
      */
     @GuardedBy("mLock")
     private int mDynamicPowerSavingsDisableThreshold;
@@ -203,7 +206,7 @@
      * Updates when Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED changes.
      */
     @GuardedBy("mLock")
-    private boolean mDynamicPowerSavingsBatterySaver;
+    private boolean mDynamicPowerSavingsEnableBatterySaver;
 
     /**
      * Last reason passed to {@link #enableBatterySaverLocked}.
@@ -265,7 +268,7 @@
     /** @return true if the dynamic mode should be used */
     private boolean isDynamicModeActiveLocked() {
         return mSettingAutomaticBatterySaver == PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC
-                && mDynamicPowerSavingsBatterySaver;
+                && mDynamicPowerSavingsEnableBatterySaver;
     }
 
     /**
@@ -428,7 +431,7 @@
         final boolean dynamicPowerSavingsThresholdChanged =
                 mDynamicPowerSavingsDisableThreshold != dynamicPowerSavingsDisableThreshold;
         final boolean dynamicPowerSavingsBatterySaverChanged =
-                mDynamicPowerSavingsBatterySaver != dynamicPowerSavingsBatterySaver;
+                mDynamicPowerSavingsEnableBatterySaver != dynamicPowerSavingsBatterySaver;
 
         if (!(enabledChanged || stickyChanged || thresholdChanged || automaticModeChanged
                 || stickyAutoDisableEnabledChanged || stickyAutoDisableThresholdChanged
@@ -443,7 +446,7 @@
         mSettingBatterySaverStickyAutoDisableThreshold = stickyAutoDisableThreshold;
         mSettingAutomaticBatterySaver = automaticBatterySaver;
         mDynamicPowerSavingsDisableThreshold = dynamicPowerSavingsDisableThreshold;
-        mDynamicPowerSavingsBatterySaver = dynamicPowerSavingsBatterySaver;
+        mDynamicPowerSavingsEnableBatterySaver = dynamicPowerSavingsBatterySaver;
 
         if (thresholdChanged) {
             // To avoid spamming the event log, we throttle logging here.
@@ -923,6 +926,8 @@
             pw.print("  mIsBatteryLevelLow=");
             pw.println(mIsBatteryLevelLow);
 
+            pw.print("  mSettingAutomaticBatterySaver=");
+            pw.println(mSettingAutomaticBatterySaver);
             pw.print("  mSettingBatterySaverEnabled=");
             pw.println(mSettingBatterySaverEnabled);
             pw.print("  mSettingBatterySaverEnabledSticky=");
@@ -936,6 +941,13 @@
             pw.print("  mBatterySaverStickyBehaviourDisabled=");
             pw.println(mBatterySaverStickyBehaviourDisabled);
 
+            pw.print("  mDynamicPowerSavingsDefaultDisableThreshold=");
+            pw.println(mDynamicPowerSavingsDefaultDisableThreshold);
+            pw.print("  mDynamicPowerSavingsDisableThreshold=");
+            pw.println(mDynamicPowerSavingsDisableThreshold);
+            pw.print("  mDynamicPowerSavingsEnableBatterySaver=");
+            pw.println(mDynamicPowerSavingsEnableBatterySaver);
+
             pw.print("  mLastAdaptiveBatterySaverChangedExternallyElapsed=");
             pw.println(mLastAdaptiveBatterySaverChangedExternallyElapsed);
         }
@@ -964,6 +976,8 @@
             proto.write(BatterySaverStateMachineProto.BATTERY_LEVEL, mBatteryLevel);
             proto.write(BatterySaverStateMachineProto.IS_BATTERY_LEVEL_LOW, mIsBatteryLevelLow);
 
+            proto.write(BatterySaverStateMachineProto.SETTING_AUTOMATIC_TRIGGER,
+                    mSettingAutomaticBatterySaver);
             proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_ENABLED,
                     mSettingBatterySaverEnabled);
             proto.write(BatterySaverStateMachineProto.SETTING_BATTERY_SAVER_ENABLED_STICKY,
@@ -979,6 +993,16 @@
                     mSettingBatterySaverStickyAutoDisableThreshold);
 
             proto.write(
+                    BatterySaverStateMachineProto.DEFAULT_DYNAMIC_DISABLE_THRESHOLD,
+                    mDynamicPowerSavingsDefaultDisableThreshold);
+            proto.write(
+                    BatterySaverStateMachineProto.DYNAMIC_DISABLE_THRESHOLD,
+                    mDynamicPowerSavingsDisableThreshold);
+            proto.write(
+                    BatterySaverStateMachineProto.DYNAMIC_BATTERY_SAVER_ENABLED,
+                    mDynamicPowerSavingsEnableBatterySaver);
+
+            proto.write(
                     BatterySaverStateMachineProto
                             .LAST_ADAPTIVE_BATTERY_SAVER_CHANGED_EXTERNALLY_ELAPSED,
                     mLastAdaptiveBatterySaverChangedExternallyElapsed);
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index a582f21..7720f7f 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -108,6 +108,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
@@ -755,6 +756,14 @@
                         || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
                 modifiedFlags |= PackageManager.MATCH_INSTANT;
             }
+            int privateResolveFlags  = 0;
+            if (intent.isWebIntent()
+                        && (intent.getFlags() & Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER) != 0) {
+                privateResolveFlags |= PackageManagerInternal.RESOLVE_NON_BROWSER_ONLY;
+            }
+            if ((intent.getFlags() & Intent.FLAG_ACTIVITY_REQUIRE_DEFAULT) != 0) {
+                privateResolveFlags |= PackageManagerInternal.RESOLVE_NON_RESOLVER_ONLY;
+            }
 
             // In order to allow cross-profile lookup, we clear the calling identity here.
             // Note the binder identity won't affect the result, but filterCallingUid will.
@@ -764,7 +773,8 @@
             final long token = Binder.clearCallingIdentity();
             try {
                 return mService.getPackageManagerInternalLocked().resolveIntent(
-                        intent, resolvedType, modifiedFlags, userId, true, filterCallingUid);
+                        intent, resolvedType, modifiedFlags, privateResolveFlags, userId, true,
+                        filterCallingUid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e1f713e..68504bd 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -912,7 +912,7 @@
 
     private void setShadowRenderer() {
         mRenderShadowsInCompositor = Settings.Global.getInt(mContext.getContentResolver(),
-                DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 0) != 0;
+                DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 1) != 0;
     }
 
     PowerManager mPowerManager;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 78d6b9631..1cfd0d4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1513,8 +1513,7 @@
         // Some system windows (e.g. "Power off" dialog) don't have a task, but we would still
         // associate them with some stack to enable dimming.
         final DisplayContent dc = getDisplayContent();
-        return mAttrs.type >= FIRST_SYSTEM_WINDOW && dc != null
-                ? dc.getOrCreateRootHomeTask() : null;
+        return mAttrs.type >= FIRST_SYSTEM_WINDOW && dc != null ? dc.getRootHomeTask() : null;
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
index e37ed79..2cbc3f3 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
@@ -124,8 +124,9 @@
         SyncOperation op2 = SyncOperation.maybeCreateFromJobExtras(pb);
 
         assertTrue("Account fields in extras not persisted.",
-                account1.equals(op2.extras.get("acc")));
-        assertTrue("Fields in extras not persisted", "String".equals(op2.extras.getString("str")));
+                account1.equals(op2.getClonedExtras().get("acc")));
+        assertTrue("Fields in extras not persisted", "String".equals(
+                op2.getClonedExtras().getString("str")));
     }
 
     @SmallTest
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
new file mode 100644
index 0000000..c4fea77
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
@@ -0,0 +1,188 @@
+/*
+ * 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.om;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.om.OverlayInfo;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceImplTestsBase {
+
+    private static final String OVERLAY = "com.dummy.overlay";
+    private static final String TARGET = "com.dummy.target";
+    private static final int USER = 0;
+
+    private static final String OVERLAY2 = OVERLAY + "2";
+
+    @Test
+    public void testUpdateOverlaysForUser() {
+        final OverlayManagerServiceImpl impl = getImpl();
+        installTargetPackage(TARGET, USER);
+        installTargetPackage("some.other.target", USER);
+        installOverlayPackage(OVERLAY, TARGET, USER);
+
+        // do nothing, expect no change
+        final List<String> a = impl.updateOverlaysForUser(USER);
+        assertEquals(1, a.size());
+        assertTrue(a.contains(TARGET));
+
+        // upgrade overlay, keep target
+        beginUpgradeOverlayPackage(OVERLAY, USER);
+        endUpgradeOverlayPackage(OVERLAY, TARGET, USER);
+
+        final List<String> b = impl.updateOverlaysForUser(USER);
+        assertEquals(1, b.size());
+        assertTrue(b.contains(TARGET));
+
+        // do nothing, expect no change
+        final List<String> c = impl.updateOverlaysForUser(USER);
+        assertEquals(1, c.size());
+        assertTrue(c.contains(TARGET));
+
+        // upgrade overlay, switch to new target
+        addOverlayPackage(OVERLAY, "some.other.target", USER, true, false, 0);
+        final List<String> d = impl.updateOverlaysForUser(USER);
+        assertEquals(2, d.size());
+        assertTrue(d.containsAll(Arrays.asList(TARGET, "some.other.target")));
+
+        // do nothing, expect no change
+        final List<String> f = impl.updateOverlaysForUser(USER);
+        assertEquals(1, f.size());
+        assertTrue(f.contains("some.other.target"));
+    }
+
+    @Test
+    public void testImmutableEnabledChange() {
+        final OverlayManagerServiceImpl impl = getImpl();
+        installTargetPackage(TARGET, USER);
+
+        addOverlayPackage(OVERLAY, TARGET, USER, false, false, 0);
+        impl.updateOverlaysForUser(USER);
+        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+        assertNotNull(o1);
+        assertFalse(o1.isEnabled());
+        assertFalse(o1.isMutable);
+
+        addOverlayPackage(OVERLAY, TARGET, USER, false, true, 0);
+        impl.updateOverlaysForUser(USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
+        assertNotNull(o2);
+        assertTrue(o2.isEnabled());
+        assertFalse(o2.isMutable);
+
+        addOverlayPackage(OVERLAY, TARGET, USER, false, false, 0);
+        impl.updateOverlaysForUser(USER);
+        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+        assertNotNull(o3);
+        assertFalse(o3.isEnabled());
+        assertFalse(o3.isMutable);
+    }
+
+    @Test
+    public void testMutableEnabledChangeHasNoEffect() {
+        final OverlayManagerServiceImpl impl = getImpl();
+        installTargetPackage(TARGET, USER);
+
+        addOverlayPackage(OVERLAY, TARGET, USER, true, false, 0);
+        impl.updateOverlaysForUser(USER);
+        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+        assertNotNull(o1);
+        assertFalse(o1.isEnabled());
+        assertTrue(o1.isMutable);
+
+        addOverlayPackage(OVERLAY, TARGET, USER, true, true, 0);
+        impl.updateOverlaysForUser(USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
+        assertNotNull(o2);
+        assertFalse(o2.isEnabled());
+        assertTrue(o2.isMutable);
+
+        addOverlayPackage(OVERLAY, TARGET, USER, true, false, 0);
+        impl.updateOverlaysForUser(USER);
+        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+        assertNotNull(o3);
+        assertFalse(o3.isEnabled());
+        assertTrue(o3.isMutable);
+    }
+
+    @Test
+    public void testMutabilityChange() {
+        final OverlayManagerServiceImpl impl = getImpl();
+        installTargetPackage(TARGET, USER);
+
+        addOverlayPackage(OVERLAY, TARGET, USER, false, true, 0);
+        impl.updateOverlaysForUser(USER);
+        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+        assertNotNull(o1);
+        assertTrue(o1.isEnabled());
+        assertFalse(o1.isMutable);
+
+        addOverlayPackage(OVERLAY, TARGET, USER, true, false, 0);
+        impl.updateOverlaysForUser(USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
+        assertNotNull(o2);
+        assertFalse(o2.isEnabled());
+        assertTrue(o2.isMutable);
+
+        addOverlayPackage(OVERLAY, TARGET, USER, false, false, 0);
+        impl.updateOverlaysForUser(USER);
+        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+        assertNotNull(o3);
+        assertFalse(o3.isEnabled());
+        assertFalse(o3.isMutable);
+    }
+
+    @Test
+    public void testPriorityChange() {
+        final OverlayManagerServiceImpl impl = getImpl();
+        installTargetPackage(TARGET, USER);
+
+        addOverlayPackage(OVERLAY, TARGET, USER, false, true, 0);
+        addOverlayPackage(OVERLAY2, TARGET, USER, false, true, 1);
+        impl.updateOverlaysForUser(USER);
+
+        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
+        assertNotNull(o1);
+        assertNotNull(o2);
+        assertEquals(0, o1.priority);
+        assertEquals(1, o2.priority);
+
+        addOverlayPackage(OVERLAY, TARGET, USER, false, true, 1);
+        addOverlayPackage(OVERLAY2, TARGET, USER, false, true, 0);
+        impl.updateOverlaysForUser(USER);
+
+        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
+        assertNotNull(o3);
+        assertNotNull(o4);
+        assertEquals(1, o3.priority);
+        assertEquals(0, o4.priority);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index c566dfc..a428a97 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -24,6 +24,9 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
 import android.content.om.OverlayInfo;
@@ -33,6 +36,8 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.content.om.OverlayConfig;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,10 +50,7 @@
 import java.util.Set;
 
 @RunWith(AndroidJUnit4.class)
-public class OverlayManagerServiceImplTests {
-    private OverlayManagerServiceImpl mImpl;
-    private DummyDeviceState mState;
-    private DummyListener mListener;
+public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTestsBase {
 
     private static final String OVERLAY = "com.dummy.overlay";
     private static final String TARGET = "com.dummy.target";
@@ -61,25 +63,14 @@
     private static final String OVERLAY3 = OVERLAY + "3";
     private static final int USER3 = USER2 + 1;
 
-
-    @Before
-    public void setUp() throws Exception {
-        mState = new DummyDeviceState();
-        mListener = new DummyListener();
-        DummyPackageManagerHelper pmh = new DummyPackageManagerHelper(mState);
-        mImpl = new OverlayManagerServiceImpl(pmh,
-                new DummyIdmapManager(mState, pmh),
-                new OverlayManagerSettings(),
-                new String[0],
-                mListener);
-    }
-
     // tests: basics
 
     @Test
     public void testGetOverlayInfo() throws Exception {
-        installOverlayPackage(OVERLAY, TARGET, USER, false);
-        final OverlayInfo oi = mImpl.getOverlayInfo(OVERLAY, USER);
+        installOverlayPackage(OVERLAY, TARGET, USER);
+
+        final OverlayManagerServiceImpl impl = getImpl();
+        final OverlayInfo oi = impl.getOverlayInfo(OVERLAY, USER);
         assertNotNull(oi);
         assertEquals(oi.packageName, OVERLAY);
         assertEquals(oi.targetPackageName, TARGET);
@@ -88,87 +79,91 @@
 
     @Test
     public void testGetOverlayInfosForTarget() throws Exception {
-        installOverlayPackage(OVERLAY, TARGET, USER, false);
-        installOverlayPackage(OVERLAY2, TARGET, USER, false);
+        installOverlayPackage(OVERLAY, TARGET, USER);
+        installOverlayPackage(OVERLAY2, TARGET, USER);
+        installOverlayPackage(OVERLAY3, TARGET, USER2);
 
-        installOverlayPackage(OVERLAY3, TARGET, USER2, false);
-
-        final List<OverlayInfo> ois = mImpl.getOverlayInfosForTarget(TARGET, USER);
+        final OverlayManagerServiceImpl impl = getImpl();
+        final List<OverlayInfo> ois = impl.getOverlayInfosForTarget(TARGET, USER);
         assertEquals(ois.size(), 2);
-        assertTrue(ois.contains(mImpl.getOverlayInfo(OVERLAY, USER)));
-        assertTrue(ois.contains(mImpl.getOverlayInfo(OVERLAY2, USER)));
+        assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY, USER)));
+        assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY2, USER)));
 
-        final List<OverlayInfo> ois2 = mImpl.getOverlayInfosForTarget(TARGET, USER2);
+        final List<OverlayInfo> ois2 = impl.getOverlayInfosForTarget(TARGET, USER2);
         assertEquals(ois2.size(), 1);
-        assertTrue(ois2.contains(mImpl.getOverlayInfo(OVERLAY3, USER2)));
+        assertTrue(ois2.contains(impl.getOverlayInfo(OVERLAY3, USER2)));
 
-        final List<OverlayInfo> ois3 = mImpl.getOverlayInfosForTarget(TARGET, USER3);
+        final List<OverlayInfo> ois3 = impl.getOverlayInfosForTarget(TARGET, USER3);
         assertNotNull(ois3);
         assertEquals(ois3.size(), 0);
 
-        final List<OverlayInfo> ois4 = mImpl.getOverlayInfosForTarget("no.such.overlay", USER);
+        final List<OverlayInfo> ois4 = impl.getOverlayInfosForTarget("no.such.overlay", USER);
         assertNotNull(ois4);
         assertEquals(ois4.size(), 0);
     }
 
     @Test
     public void testGetOverlayInfosForUser() throws Exception {
-        installOverlayPackage(OVERLAY, TARGET, USER, false);
-        installOverlayPackage(OVERLAY2, TARGET, USER, false);
-        installOverlayPackage(OVERLAY3, TARGET2, USER, false);
+        installTargetPackage(TARGET, USER);
+        installOverlayPackage(OVERLAY, TARGET, USER);
+        installOverlayPackage(OVERLAY2, TARGET, USER);
+        installOverlayPackage(OVERLAY3, TARGET2, USER);
 
-        final Map<String, List<OverlayInfo>> everything = mImpl.getOverlaysForUser(USER);
+        final OverlayManagerServiceImpl impl = getImpl();
+        final Map<String, List<OverlayInfo>> everything = impl.getOverlaysForUser(USER);
         assertEquals(everything.size(), 2);
 
         final List<OverlayInfo> ois = everything.get(TARGET);
         assertNotNull(ois);
         assertEquals(ois.size(), 2);
-        assertTrue(ois.contains(mImpl.getOverlayInfo(OVERLAY, USER)));
-        assertTrue(ois.contains(mImpl.getOverlayInfo(OVERLAY2, USER)));
+        assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY, USER)));
+        assertTrue(ois.contains(impl.getOverlayInfo(OVERLAY2, USER)));
 
         final List<OverlayInfo> ois2 = everything.get(TARGET2);
         assertNotNull(ois2);
         assertEquals(ois2.size(), 1);
-        assertTrue(ois2.contains(mImpl.getOverlayInfo(OVERLAY3, USER)));
+        assertTrue(ois2.contains(impl.getOverlayInfo(OVERLAY3, USER)));
 
-        final Map<String, List<OverlayInfo>> everything2 = mImpl.getOverlaysForUser(USER2);
+        final Map<String, List<OverlayInfo>> everything2 = impl.getOverlaysForUser(USER2);
         assertNotNull(everything2);
         assertEquals(everything2.size(), 0);
     }
 
     @Test
     public void testPriority() throws Exception {
-        installOverlayPackage(OVERLAY, TARGET, USER, false);
-        installOverlayPackage(OVERLAY2, TARGET, USER, false);
-        installOverlayPackage(OVERLAY3, TARGET, USER, false);
+        installOverlayPackage(OVERLAY, TARGET, USER);
+        installOverlayPackage(OVERLAY2, TARGET, USER);
+        installOverlayPackage(OVERLAY3, TARGET, USER);
 
-        final OverlayInfo o1 = mImpl.getOverlayInfo(OVERLAY, USER);
-        final OverlayInfo o2 = mImpl.getOverlayInfo(OVERLAY2, USER);
-        final OverlayInfo o3 = mImpl.getOverlayInfo(OVERLAY3, USER);
+        final OverlayManagerServiceImpl impl = getImpl();
+        final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
+        final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
+        final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY3, USER);
 
         assertOverlayInfoList(TARGET, USER, o1, o2, o3);
 
-        assertTrue(mImpl.setLowestPriority(OVERLAY3, USER));
+        assertTrue(impl.setLowestPriority(OVERLAY3, USER));
         assertOverlayInfoList(TARGET, USER, o3, o1, o2);
 
-        assertTrue(mImpl.setHighestPriority(OVERLAY3, USER));
+        assertTrue(impl.setHighestPriority(OVERLAY3, USER));
         assertOverlayInfoList(TARGET, USER, o1, o2, o3);
 
-        assertTrue(mImpl.setPriority(OVERLAY, OVERLAY2, USER));
+        assertTrue(impl.setPriority(OVERLAY, OVERLAY2, USER));
         assertOverlayInfoList(TARGET, USER, o2, o1, o3);
     }
 
     @Test
     public void testOverlayInfoStateTransitions() throws Exception {
-        assertNull(mImpl.getOverlayInfo(OVERLAY, USER));
+        final OverlayManagerServiceImpl impl = getImpl();
+        assertNull(impl.getOverlayInfo(OVERLAY, USER));
 
-        installOverlayPackage(OVERLAY, TARGET, USER, true);
+        installOverlayPackage(OVERLAY, TARGET, USER);
         assertState(STATE_MISSING_TARGET, OVERLAY, USER);
 
         installTargetPackage(TARGET, USER);
         assertState(STATE_DISABLED, OVERLAY, USER);
 
-        mImpl.setEnabled(OVERLAY, true, USER);
+        impl.setEnabled(OVERLAY, true, USER);
         assertState(STATE_ENABLED, OVERLAY, USER);
 
         // target upgrades do not change the state of the overlay
@@ -186,319 +181,48 @@
     }
 
     @Test
-    public void testUpdateOverlaysForUser() throws Exception {
-        installTargetPackage(TARGET, USER);
-        installTargetPackage("some.other.target", USER);
-        installOverlayPackage(OVERLAY, TARGET, USER, true);
-
-        // do nothing, expect no change
-        List<String> a = mImpl.updateOverlaysForUser(USER);
-        assertEquals(1, a.size());
-        assertTrue(a.contains(TARGET));
-
-        // upgrade overlay, keep target
-        upgradeOverlayPackage(OVERLAY, TARGET, USER, true);
-        List<String> b = mImpl.updateOverlaysForUser(USER);
-        assertEquals(1, b.size());
-        assertTrue(b.contains(TARGET));
-
-        // do nothing, expect no change
-        List<String> c = mImpl.updateOverlaysForUser(USER);
-        assertEquals(1, c.size());
-        assertTrue(c.contains(TARGET));
-
-        // upgrade overlay, switch to new target
-        upgradeOverlayPackage(OVERLAY, "some.other.target", USER, true);
-        List<String> d = mImpl.updateOverlaysForUser(USER);
-        assertEquals(2, d.size());
-        assertTrue(d.containsAll(Arrays.asList(TARGET, "some.other.target")));
-
-        // do nothing, expect no change
-        List<String> e = mImpl.updateOverlaysForUser(USER);
-        assertEquals(1, e.size());
-        assertTrue(e.contains("some.other.target"));
-    }
-
-    @Test
     public void testOnOverlayPackageUpgraded() throws Exception {
+        final OverlayManagerServiceImpl impl = getImpl();
+        final DummyListener listener = getListener();
         installTargetPackage(TARGET, USER);
-        installOverlayPackage(OVERLAY, TARGET, USER, true);
-        mImpl.onOverlayPackageReplacing(OVERLAY, USER);
-        mListener.count = 0;
-        mImpl.onOverlayPackageReplaced(OVERLAY, USER);
-        assertEquals(1, mListener.count);
+        installOverlayPackage(OVERLAY, TARGET, USER);
+        impl.onOverlayPackageReplacing(OVERLAY, USER);
+        listener.count = 0;
+        impl.onOverlayPackageReplaced(OVERLAY, USER);
+        assertEquals(1, listener.count);
 
         // upgrade to a version where the overlay has changed its target
-        upgradeOverlayPackage(OVERLAY, "some.other.target", USER, true);
-        mImpl.onOverlayPackageReplacing(OVERLAY, USER);
-        mListener.count = 0;
-        mImpl.onOverlayPackageReplaced(OVERLAY, USER);
+        beginUpgradeOverlayPackage(OVERLAY, USER);
+        listener.count = 0;
+        endUpgradeOverlayPackage(OVERLAY, "some.other.target", USER);
         // expect once for the old target package, once for the new target package
-        assertEquals(2, mListener.count);
+        assertEquals(2, listener.count);
 
-        upgradeOverlayPackage(OVERLAY, "some.other.target", USER, true);
-        mImpl.onOverlayPackageReplacing(OVERLAY, USER);
-        mListener.count = 0;
-        mImpl.onOverlayPackageReplaced(OVERLAY, USER);
-        assertEquals(1, mListener.count);
+        beginUpgradeOverlayPackage(OVERLAY, USER);
+        listener.count = 0;
+        endUpgradeOverlayPackage(OVERLAY, "some.other.target", USER);
+        assertEquals(1, listener.count);
     }
 
     // tests: listener interface
 
     @Test
     public void testListener() throws Exception {
-        installOverlayPackage(OVERLAY, TARGET, USER, true);
-        assertEquals(1, mListener.count);
-        mListener.count = 0;
+        final OverlayManagerServiceImpl impl = getImpl();
+        final DummyListener listener = getListener();
+        installOverlayPackage(OVERLAY, TARGET, USER);
+        assertEquals(1, listener.count);
+        listener.count = 0;
 
         installTargetPackage(TARGET, USER);
-        assertEquals(1, mListener.count);
-        mListener.count = 0;
+        assertEquals(1, listener.count);
+        listener.count = 0;
 
-        mImpl.setEnabled(OVERLAY, true, USER);
-        assertEquals(1, mListener.count);
-        mListener.count = 0;
+        impl.setEnabled(OVERLAY, true, USER);
+        assertEquals(1, listener.count);
+        listener.count = 0;
 
-        mImpl.setEnabled(OVERLAY, true, USER);
-        assertEquals(0, mListener.count);
-    }
-
-    // helper methods
-
-    private void assertState(int expected, final String overlayPackageName, int userId) {
-        int actual = mImpl.getOverlayInfo(OVERLAY, USER).state;
-        String msg = String.format("expected %s but was %s:",
-                OverlayInfo.stateToString(expected), OverlayInfo.stateToString(actual));
-        assertEquals(msg, expected, actual);
-    }
-
-    private void assertOverlayInfoList(final String targetPackageName, int userId,
-            OverlayInfo... overlayInfos) {
-        final List<OverlayInfo> expected =
-                mImpl.getOverlayInfosForTarget(targetPackageName, userId);
-        final List<OverlayInfo> actual = Arrays.asList(overlayInfos);
-        assertEquals(expected, actual);
-    }
-
-    private void installTargetPackage(String packageName, int userId) {
-        if (mState.select(packageName, userId) != null) {
-            throw new IllegalStateException("package already installed");
-        }
-        mState.add(packageName, null, userId, false);
-        mImpl.onTargetPackageAdded(packageName, userId);
-    }
-
-    private void beginUpgradeTargetPackage(String packageName, int userId) {
-        if (mState.select(packageName, userId) == null) {
-            throw new IllegalStateException("package not installed");
-        }
-        mState.add(packageName, null, userId, false);
-        mImpl.onTargetPackageReplacing(packageName, userId);
-    }
-
-    private void endUpgradeTargetPackage(String packageName, int userId) {
-        if (mState.select(packageName, userId) == null) {
-            throw new IllegalStateException("package not installed");
-        }
-        mState.add(packageName, null, userId, false);
-        mImpl.onTargetPackageReplaced(packageName, userId);
-    }
-
-    private void uninstallTargetPackage(String packageName, int userId) {
-        if (mState.select(packageName, userId) == null) {
-            throw new IllegalStateException("package not installed");
-        }
-        mState.remove(packageName, userId);
-        mImpl.onTargetPackageRemoved(packageName, userId);
-    }
-
-    private void installOverlayPackage(String packageName, String targetPackageName, int userId,
-            boolean canCreateIdmap) {
-        if (mState.select(packageName, userId) != null) {
-            throw new IllegalStateException("package already installed");
-        }
-        mState.add(packageName, targetPackageName, userId, canCreateIdmap);
-        mImpl.onOverlayPackageAdded(packageName, userId);
-    }
-
-    private void upgradeOverlayPackage(String packageName, String targetPackageName, int userId,
-            boolean canCreateIdmap) {
-        DummyDeviceState.Package pkg = mState.select(packageName, userId);
-        if (pkg == null) {
-            throw new IllegalStateException("package not installed, cannot upgrade");
-        }
-        pkg.targetPackageName = targetPackageName;
-        pkg.canCreateIdmap = canCreateIdmap;
-    }
-
-    private void uninstallOverlayPackage(String packageName, int userId) {
-        // implement this when adding support for downloadable overlays
-        throw new IllegalArgumentException("not implemented");
-    }
-
-    private static final class DummyDeviceState {
-        private List<Package> mPackages = new ArrayList<>();
-
-        public void add(String packageName, String targetPackageName, int userId,
-                boolean canCreateIdmap) {
-            remove(packageName, userId);
-            Package pkg = new Package();
-            pkg.packageName = packageName;
-            pkg.targetPackageName = targetPackageName;
-            pkg.userId = userId;
-            pkg.canCreateIdmap = canCreateIdmap;
-            mPackages.add(pkg);
-        }
-
-        public void remove(String packageName, int userId) {
-            final Iterator<Package> iter = mPackages.iterator();
-            while (iter.hasNext()) {
-                final Package pkg = iter.next();
-                if (pkg.packageName.equals(packageName) && pkg.userId == userId) {
-                    iter.remove();
-                    return;
-                }
-            }
-        }
-
-        public List<Package> select(int userId) {
-            List<Package> out = new ArrayList<>();
-            final int packageCount = mPackages.size();
-            for (int i = 0; i < packageCount; i++) {
-                final Package pkg = mPackages.get(i);
-                if (pkg.userId == userId) {
-                    out.add(pkg);
-                }
-            }
-            return out;
-        }
-
-        public Package select(String packageName, int userId) {
-            final int packageCount = mPackages.size();
-            for (int i = 0; i < packageCount; i++) {
-                final Package pkg = mPackages.get(i);
-                if (pkg.packageName.equals(packageName) && pkg.userId == userId) {
-                    return pkg;
-                }
-            }
-            return null;
-        }
-
-        private static final class Package {
-            public String packageName;
-            public int userId;
-            public String targetPackageName;
-            public boolean canCreateIdmap;
-        }
-    }
-
-    private static final class DummyPackageManagerHelper implements
-            OverlayManagerServiceImpl.PackageManagerHelper {
-        private final DummyDeviceState mState;
-
-        DummyPackageManagerHelper(DummyDeviceState state) {
-            mState = state;
-        }
-
-        @Override
-        public PackageInfo getPackageInfo(@NonNull String packageName, int userId) {
-            final DummyDeviceState.Package pkg = mState.select(packageName, userId);
-            if (pkg == null) {
-                return null;
-            }
-            ApplicationInfo ai = new ApplicationInfo();
-            ai.sourceDir = String.format("%s/%s/base.apk",
-                    pkg.targetPackageName == null ? "/system/app/" : "/vendor/overlay/",
-                    pkg.packageName);
-            PackageInfo pi = new PackageInfo();
-            pi.applicationInfo = ai;
-            pi.packageName = pkg.packageName;
-            pi.overlayTarget = pkg.targetPackageName;
-            pi.overlayCategory = "dummy-category-" + pkg.targetPackageName;
-            return pi;
-        }
-
-        @Override
-        public boolean signaturesMatching(@NonNull String packageName1,
-                @NonNull String packageName2, int userId) {
-            return false;
-        }
-
-        @Override
-        public List<PackageInfo> getOverlayPackages(int userId) {
-            List<PackageInfo> out = new ArrayList<>();
-            final List<DummyDeviceState.Package> packages = mState.select(userId);
-            final int packageCount = packages.size();
-            for (int i = 0; i < packageCount; i++) {
-                final DummyDeviceState.Package pkg = packages.get(i);
-                if (pkg.targetPackageName != null) {
-                    out.add(getPackageInfo(pkg.packageName, pkg.userId));
-                }
-            }
-            return out;
-        }
-    }
-
-    private static class DummyIdmapManager extends IdmapManager {
-        private final DummyDeviceState mState;
-        private Set<String> mIdmapFiles = new ArraySet<>();
-
-        DummyIdmapManager(DummyDeviceState state, DummyPackageManagerHelper packageManagerHelper) {
-            super(packageManagerHelper);
-            mState = state;
-        }
-
-        @Override
-        boolean createIdmap(@NonNull final PackageInfo targetPackage,
-                @NonNull final PackageInfo overlayPackage, int userId) {
-            final DummyDeviceState.Package t = mState.select(targetPackage.packageName, userId);
-            if (t == null) {
-                return false;
-            }
-            final DummyDeviceState.Package o = mState.select(overlayPackage.packageName, userId);
-            if (o == null) {
-                return false;
-            }
-            if (!o.canCreateIdmap) {
-                return false;
-            }
-            final String key = createKey(overlayPackage.packageName, userId);
-            mIdmapFiles.add(key);
-            return true;
-        }
-
-        @Override
-        boolean removeIdmap(@NonNull final OverlayInfo oi, final int userId) {
-            final String key = createKey(oi.packageName, oi.userId);
-            if (!mIdmapFiles.contains(key)) {
-                return false;
-            }
-            mIdmapFiles.remove(key);
-            return true;
-        }
-
-        @Override
-        boolean idmapExists(@NonNull final OverlayInfo oi) {
-            final String key = createKey(oi.packageName, oi.userId);
-            return mIdmapFiles.contains(key);
-        }
-
-        @Override
-        boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
-            final String key = createKey(overlayPackage.packageName, userId);
-            return mIdmapFiles.contains(key);
-        }
-
-        private String createKey(@NonNull final String packageName, final int userId) {
-            return String.format("%s:%d", packageName, userId);
-        }
-    }
-
-    private static class DummyListener implements OverlayManagerServiceImpl.OverlayChangeListener {
-        public int count;
-
-        public void onOverlaysChanged(@NonNull String targetPackage, int userId) {
-            count++;
-        }
+        impl.setEnabled(OVERLAY, true, USER);
+        assertEquals(0, listener.count);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
new file mode 100644
index 0000000..a753aac
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -0,0 +1,385 @@
+/*
+ * 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.om;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayInfo.State;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.util.ArraySet;
+
+import com.android.internal.content.om.OverlayConfig;
+
+import org.junit.Before;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/** Base class for creating {@link OverlayManagerServiceImplTests} tests. */
+class OverlayManagerServiceImplTestsBase {
+    private OverlayManagerServiceImpl mImpl;
+    private DummyDeviceState mState;
+    private DummyListener mListener;
+
+    @Before
+    public void setUp() {
+        mState = new DummyDeviceState();
+        mListener = new DummyListener();
+        final DummyPackageManagerHelper pmh = new DummyPackageManagerHelper(mState);
+
+        mImpl = new OverlayManagerServiceImpl(pmh,
+                new DummyIdmapManager(mState, pmh),
+                new OverlayManagerSettings(),
+                mState.mOverlayConfig,
+                new String[0],
+                mListener);
+    }
+
+    public OverlayManagerServiceImpl getImpl() {
+        return mImpl;
+    }
+
+    public DummyListener getListener() {
+        return mListener;
+    }
+
+    void assertState(@State int expected, final String overlayPackageName, int userId) {
+        final OverlayInfo info = mImpl.getOverlayInfo(overlayPackageName, userId);
+        if (info == null) {
+            throw new IllegalStateException("package not installed");
+        }
+
+        final String msg = String.format("expected %s but was %s:",
+                OverlayInfo.stateToString(expected), OverlayInfo.stateToString(info.state));
+        assertEquals(msg, expected, info.state);
+    }
+
+    void assertOverlayInfoList(final String targetPackageName, int userId,
+            OverlayInfo... overlayInfos) {
+        final List<OverlayInfo> expected =
+                mImpl.getOverlayInfosForTarget(targetPackageName, userId);
+        final List<OverlayInfo> actual = Arrays.asList(overlayInfos);
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Creates an overlay configured through {@link OverlayConfig}.
+     *
+     * @throws IllegalStateException if the package is already installed
+     */
+    void addOverlayPackage(String packageName, String targetPackageName, int userId,
+            boolean mutable, boolean enabled, int priority) {
+        mState.addOverlay(packageName, targetPackageName, userId, mutable, enabled, priority);
+    }
+
+    /**
+     * Adds the target package to the device.
+     *
+     * This corresponds to when the OMS receives the
+     * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast.
+     *
+     * @throws IllegalStateException if the package is not currently installed
+     */
+    void installTargetPackage(String packageName, int userId) {
+        if (mState.select(packageName, userId) != null) {
+            throw new IllegalStateException("package already installed");
+        }
+        mState.addTarget(packageName, userId);
+        mImpl.onTargetPackageAdded(packageName, userId);
+    }
+
+    /**
+     * Begins upgrading the target package.
+     *
+     * This corresponds to when the OMS receives the
+     * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast with the
+     * {@link android.content.Intent#EXTRA_REPLACING} extra.
+     *
+     * @throws IllegalStateException if the package is not currently installed
+     */
+    void beginUpgradeTargetPackage(String packageName, int userId) {
+        if (mState.select(packageName, userId) == null) {
+            throw new IllegalStateException("package not installed");
+        }
+        mImpl.onTargetPackageReplacing(packageName, userId);
+    }
+
+    /**
+     * Ends upgrading the target package.
+     *
+     * This corresponds to when the OMS receives the
+     * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the
+     * {@link android.content.Intent#EXTRA_REPLACING} extra.
+     *
+     * @throws IllegalStateException if the package is not currently installed
+     */
+    void endUpgradeTargetPackage(String packageName, int userId) {
+        if (mState.select(packageName, userId) == null) {
+            throw new IllegalStateException("package not installed");
+        }
+        mState.addTarget(packageName, userId);
+        mImpl.onTargetPackageReplaced(packageName, userId);
+    }
+
+    /**
+     * Removes the target package from the device.
+     *
+     * This corresponds to when the OMS receives the
+     * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast.
+     *
+     * @throws IllegalStateException if the package is not currently installed
+     */
+    void uninstallTargetPackage(String packageName, int userId) {
+        if (mState.select(packageName, userId) == null) {
+            throw new IllegalStateException("package not installed");
+        }
+        mState.remove(packageName, userId);
+        mImpl.onTargetPackageRemoved(packageName, userId);
+    }
+
+    /**
+     * Adds the overlay package to the device.
+     *
+     * This corresponds to when the OMS receives the
+     * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast.
+     *
+     * @throws IllegalStateException if the package is already installed
+     */
+    void installOverlayPackage(String packageName, String targetPackageName, int userId) {
+        if (mState.select(packageName, userId) != null) {
+            throw new IllegalStateException("package already installed");
+        }
+        mState.addOverlay(packageName, targetPackageName, userId);
+        mImpl.onOverlayPackageAdded(packageName, userId);
+    }
+
+    /**
+     * Begins upgrading the overlay package.
+     *
+     * This corresponds to when the OMS receives the
+     * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast with the
+     * {@link android.content.Intent#EXTRA_REPLACING} extra.
+     *
+     * @throws IllegalStateException if the package is not currently installed
+     */
+    void beginUpgradeOverlayPackage(String packageName, int userId) {
+        if (mState.select(packageName, userId) == null) {
+            throw new IllegalStateException("package not installed, cannot upgrade");
+        }
+
+        mImpl.onOverlayPackageReplacing(packageName, userId);
+    }
+
+    /**
+     * Ends upgrading the overlay package, potentially changing its target package.
+     *
+     * This corresponds to when the OMS receives the
+     * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the
+     * {@link android.content.Intent#EXTRA_REPLACING} extra.
+     *
+     * @throws IllegalStateException if the package is not currently installed
+     */
+    void endUpgradeOverlayPackage(String packageName, String targetPackageName, int userId) {
+        if (mState.select(packageName, userId) == null) {
+            throw new IllegalStateException("package not installed, cannot upgrade");
+        }
+
+        mState.addOverlay(packageName, targetPackageName, userId);
+        mImpl.onOverlayPackageReplaced(packageName, userId);
+    }
+
+    private static final class DummyDeviceState {
+        private List<Package> mPackages = new ArrayList<>();
+        private OverlayConfig mOverlayConfig = mock(OverlayConfig.class);
+
+        /** Adds a non-overlay to the device. */
+        public void addTarget(String packageName, int userId) {
+            remove(packageName, userId);
+            mPackages.add(new Package(packageName, userId, null, false, false, 0));
+        }
+
+        /** Adds an overlay to the device. */
+        public void addOverlay(String packageName, String targetPackageName, int userId) {
+            addOverlay(packageName, targetPackageName, userId, true, false, OverlayConfig.DEFAULT_PRIORITY);
+        }
+
+        /** Adds a configured overlay to the device. */
+        public void addOverlay(String packageName, String targetPackageName, int userId,
+                boolean mutable, boolean enabled, int priority) {
+            remove(packageName, userId);
+            mPackages.add(new Package(packageName, userId, targetPackageName, mutable, enabled,
+                    priority));
+            when(mOverlayConfig.getPriority(packageName)).thenReturn(priority);
+            when(mOverlayConfig.isEnabled(packageName)).thenReturn(enabled);
+            when(mOverlayConfig.isMutable(packageName)).thenReturn(mutable);
+        }
+
+        /** Remove a package from the device. */
+        public void remove(String packageName, int userId) {
+            final Iterator<Package> iter = mPackages.iterator();
+            while (iter.hasNext()) {
+                final Package pkg = iter.next();
+                if (pkg.packageName.equals(packageName) && pkg.userId == userId) {
+                    iter.remove();
+                    return;
+                }
+            }
+        }
+
+        /** Retrieves all packages on device for a particular user. */
+        public List<Package> select(int userId) {
+            return mPackages.stream().filter(p -> p.userId == userId).collect(Collectors.toList());
+        }
+
+        /** Retrieves the package with the specified package name for a particular user. */
+        public Package select(String packageName, int userId) {
+            return mPackages.stream().filter(
+                    p -> p.packageName.equals(packageName) && p.userId == userId)
+                    .findFirst().orElse(null);
+        }
+
+        private static final class Package {
+            public final String packageName;
+            public final int userId;
+            public final String targetPackageName;
+            public final boolean mutable;
+            public final boolean enabled;
+            public final int priority;
+
+            private Package(String packageName, int userId, String targetPackageName,
+                    boolean mutable, boolean enabled, int priority) {
+                this.packageName = packageName;
+                this.userId = userId;
+                this.targetPackageName = targetPackageName;
+                this.mutable = mutable;
+                this.enabled = enabled;
+                this.priority = priority;
+            }
+        }
+    }
+
+    static final class DummyPackageManagerHelper implements
+            OverlayManagerServiceImpl.PackageManagerHelper {
+        private final DummyDeviceState mState;
+
+        private DummyPackageManagerHelper(DummyDeviceState state) {
+            mState = state;
+        }
+
+        @Override
+        public PackageInfo getPackageInfo(@NonNull String packageName, int userId) {
+            final DummyDeviceState.Package pkg = mState.select(packageName, userId);
+            if (pkg == null) {
+                return null;
+            }
+            final ApplicationInfo ai = new ApplicationInfo();
+            ai.sourceDir = String.format("%s/%s/base.apk",
+                    pkg.targetPackageName == null ? "/system/app/" : "/vendor/overlay/",
+                    pkg.packageName);
+            PackageInfo pi = new PackageInfo();
+            pi.applicationInfo = ai;
+            pi.packageName = pkg.packageName;
+            pi.overlayTarget = pkg.targetPackageName;
+            pi.overlayCategory = "dummy-category-" + pkg.targetPackageName;
+            return pi;
+        }
+
+        @Override
+        public boolean signaturesMatching(@NonNull String packageName1,
+                @NonNull String packageName2, int userId) {
+            return false;
+        }
+
+        @Override
+        public List<PackageInfo> getOverlayPackages(int userId) {
+            return mState.select(userId).stream()
+                    .filter(p -> p.targetPackageName != null)
+                    .map(p -> getPackageInfo(p.packageName, p.userId))
+                    .collect(Collectors.toList());
+        }
+    }
+
+    static class DummyIdmapManager extends IdmapManager {
+        private final DummyDeviceState mState;
+        private Set<String> mIdmapFiles = new ArraySet<>();
+
+        private DummyIdmapManager(DummyDeviceState state,
+                DummyPackageManagerHelper packageManagerHelper) {
+            super(packageManagerHelper);
+            mState = state;
+        }
+
+        @Override
+        boolean createIdmap(@NonNull final PackageInfo targetPackage,
+                @NonNull final PackageInfo overlayPackage, int userId) {
+            final DummyDeviceState.Package t = mState.select(targetPackage.packageName, userId);
+            if (t == null) {
+                return false;
+            }
+            final DummyDeviceState.Package o = mState.select(overlayPackage.packageName, userId);
+            if (o == null) {
+                return false;
+            }
+            final String key = createKey(overlayPackage.packageName, userId);
+            mIdmapFiles.add(key);
+            return true;
+        }
+
+        @Override
+        boolean removeIdmap(@NonNull final OverlayInfo oi, final int userId) {
+            final String key = createKey(oi.packageName, oi.userId);
+            if (!mIdmapFiles.contains(key)) {
+                return false;
+            }
+            mIdmapFiles.remove(key);
+            return true;
+        }
+
+        @Override
+        boolean idmapExists(@NonNull final OverlayInfo oi) {
+            final String key = createKey(oi.packageName, oi.userId);
+            return mIdmapFiles.contains(key);
+        }
+
+        @Override
+        boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
+            final String key = createKey(overlayPackage.packageName, userId);
+            return mIdmapFiles.contains(key);
+        }
+
+        private String createKey(@NonNull final String packageName, final int userId) {
+            return String.format("%s:%d", packageName, userId);
+        }
+    }
+
+    static class DummyListener implements OverlayManagerServiceImpl.OverlayChangeListener {
+        public int count;
+
+        public void onOverlaysChanged(@NonNull String targetPackage, int userId) {
+            count++;
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
index 8ff8b6e..146f60a 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerSettingsTests.java
@@ -58,7 +58,7 @@
             STATE_DISABLED,
             0,
             0,
-            false);
+            true);
 
     private static final OverlayInfo OVERLAY_B0 = new OverlayInfo(
             "com.dummy.overlay_b",
@@ -69,7 +69,7 @@
             STATE_DISABLED,
             0,
             0,
-            false);
+            true);
 
     private static final OverlayInfo OVERLAY_C0 = new OverlayInfo(
             "com.dummy.overlay_c",
@@ -80,7 +80,7 @@
             STATE_DISABLED,
             0,
             0,
-            false);
+            true);
 
     private static final OverlayInfo OVERLAY_A1 = new OverlayInfo(
             "com.dummy.overlay_a",
@@ -91,7 +91,7 @@
             STATE_DISABLED,
             1,
             0,
-            false);
+            true);
 
     private static final OverlayInfo OVERLAY_B1 = new OverlayInfo(
             "com.dummy.overlay_b",
@@ -102,7 +102,7 @@
             STATE_DISABLED,
             1,
             0,
-            false);
+            true);
 
     @Before
     public void setUp() throws Exception {
@@ -238,7 +238,7 @@
                 STATE_DISABLED,
                 0,
                 0,
-                false);
+                true);
         insert(otherTarget);
         changed = mSettings.setPriority(OVERLAY_A0.packageName, otherTarget.packageName,
                 OVERLAY_A0.userId);
@@ -435,7 +435,7 @@
 
     private void insert(OverlayInfo oi) throws Exception {
         mSettings.init(oi.packageName, oi.userId, oi.targetPackageName, null, oi.baseCodePath,
-                false, 0, oi.category);
+                true, false,0, oi.category);
         mSettings.setState(oi.packageName, oi.userId, oi.state);
         mSettings.setEnabled(oi.packageName, oi.userId, false);
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
index 85840e1..5c15322 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -118,15 +118,15 @@
         String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" };
         String[] appdir = { "app", "priv-app" };
         for (int i = 0; i < partitions.length; i++) {
-            final PackageManagerService.SystemPartition systemPartition =
+            final PackageManagerService.ScanPartition scanPartition =
                     PackageManagerService.SYSTEM_PARTITIONS.get(i);
             for (int j = 0; j < appdir.length; j++) {
                 String canonical = new File("/" + partitions[i]).getCanonicalPath();
                 String path = String.format("%s/%s/A.apk", canonical, appdir[j]);
 
-                Assert.assertEquals(j == 1 && i != 3, systemPartition.containsPrivPath(path));
+                Assert.assertEquals(j == 1 && i != 3, scanPartition.containsPrivPath(path));
 
-                final int scanFlag = systemPartition.scanFlag;
+                final int scanFlag = scanPartition.scanFlag;
                 Assert.assertEquals(i == 1, scanFlag == PackageManagerService.SCAN_AS_VENDOR);
                 Assert.assertEquals(i == 2, scanFlag == PackageManagerService.SCAN_AS_ODM);
                 Assert.assertEquals(i == 3, scanFlag == PackageManagerService.SCAN_AS_OEM);
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 e5ffb4d..604fcd3 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -2902,21 +2902,21 @@
     }
 
     @Test
-    public void testSetListenerAccess_doesNothingOnLowRam() throws Exception {
+    public void testSetListenerAccess_onLowRam() throws Exception {
         when(mActivityManager.isLowRamDevice()).thenReturn(true);
         ComponentName c = ComponentName.unflattenFromString("package/Component");
         mBinderService.setNotificationListenerAccessGranted(c, true);
 
-        verify(mListeners, never()).setPackageOrComponentEnabled(
+        verify(mListeners).setPackageOrComponentEnabled(
                 anyString(), anyInt(), anyBoolean(), anyBoolean());
-        verify(mConditionProviders, never()).setPackageOrComponentEnabled(
+        verify(mConditionProviders).setPackageOrComponentEnabled(
                 anyString(), anyInt(), anyBoolean(), anyBoolean());
-        verify(mAssistants, never()).setPackageOrComponentEnabled(
-                any(), anyInt(), anyBoolean(), anyBoolean());
+        verify(mAssistants).migrateToXml();
+        verify(mAssistants).resetDefaultAssistantsIfNecessary();
     }
 
     @Test
-    public void testSetAssistantAccess_doesNothingOnLowRam() throws Exception {
+    public void testSetAssistantAccess_onLowRam() throws Exception {
         when(mActivityManager.isLowRamDevice()).thenReturn(true);
         ComponentName c = ComponentName.unflattenFromString("package/Component");
         List<UserInfo> uis = new ArrayList<>();
@@ -2927,26 +2927,28 @@
 
         mBinderService.setNotificationAssistantAccessGranted(c, true);
 
-        verify(mListeners, never()).setPackageOrComponentEnabled(
+        verify(mListeners).migrateToXml();
+        verify(mListeners).notifyNotificationChannelChanged(anyString(), any(), any(),
+                anyInt());
+        verify(mConditionProviders).setPackageOrComponentEnabled(
                 anyString(), anyInt(), anyBoolean(), anyBoolean());
-        verify(mConditionProviders, never()).setPackageOrComponentEnabled(
-                anyString(), anyInt(), anyBoolean(), anyBoolean());
-        verify(mAssistants, never()).setPackageOrComponentEnabled(
-                any(), anyInt(), anyBoolean(), anyBoolean());
+        verify(mAssistants).migrateToXml();
+        verify(mAssistants).resetDefaultAssistantsIfNecessary();
     }
 
     @Test
-    public void testSetDndAccess_doesNothingOnLowRam() throws Exception {
+    public void testSetDndAccess_onLowRam() throws Exception {
         when(mActivityManager.isLowRamDevice()).thenReturn(true);
         ComponentName c = ComponentName.unflattenFromString("package/Component");
         mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true);
 
-        verify(mListeners, never()).setPackageOrComponentEnabled(
+        verify(mListeners).migrateToXml();
+        verify(mListeners).notifyNotificationChannelChanged(anyString(), any(), any(),
+                anyInt());
+        verify(mConditionProviders).setPackageOrComponentEnabled(
                 anyString(), anyInt(), anyBoolean(), anyBoolean());
-        verify(mConditionProviders, never()).setPackageOrComponentEnabled(
-                anyString(), anyInt(), anyBoolean(), anyBoolean());
-        verify(mAssistants, never()).setPackageOrComponentEnabled(
-                any(), anyInt(), anyBoolean(), anyBoolean());
+        verify(mAssistants).migrateToXml();
+        verify(mAssistants).resetDefaultAssistantsIfNecessary();
     }
 
     @Test
@@ -4288,68 +4290,13 @@
     }
 
     @Test
-    public void testCanUseManagedServicesLowRamNoWatchNullPkg() {
-        when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
-        when(mActivityManager.isLowRamDevice()).thenReturn(true);
-        when(mResources.getStringArray(R.array.config_allowedManagedServicesOnLowRamDevices))
-                .thenReturn(new String[] {"a", "b", "c"});
-        when(mContext.getResources()).thenReturn(mResources);
-
-        assertEquals(false, mService.canUseManagedServices(null, 0, null));
+    public void testCanUseManagedServicesNullPkg() {
+        assertEquals(true, mService.canUseManagedServices(null, 0, null));
     }
 
-    @Test
-    public void testCanUseManagedServicesLowRamNoWatchValidPkg() {
-        when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
-        when(mActivityManager.isLowRamDevice()).thenReturn(true);
-        when(mResources.getStringArray(R.array.config_allowedManagedServicesOnLowRamDevices))
-                .thenReturn(new String[] {"a", "b", "c"});
-        when(mContext.getResources()).thenReturn(mResources);
-
-        assertEquals(true, mService.canUseManagedServices("b", 0, null));
-    }
 
     @Test
-    public void testCanUseManagedServicesLowRamNoWatchNoValidPkg() {
-        when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
-        when(mActivityManager.isLowRamDevice()).thenReturn(true);
-        when(mResources.getStringArray(R.array.config_allowedManagedServicesOnLowRamDevices))
-                .thenReturn(new String[] {"a", "b", "c"});
-        when(mContext.getResources()).thenReturn(mResources);
-
-        assertEquals(false, mService.canUseManagedServices("d", 0, null));
-    }
-
-    @Test
-    public void testCanUseManagedServicesLowRamWatchNoValidPkg() {
-        when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(true);
-        when(mActivityManager.isLowRamDevice()).thenReturn(true);
-        when(mResources.getStringArray(R.array.config_allowedManagedServicesOnLowRamDevices))
-                .thenReturn(new String[] {"a", "b", "c"});
-        when(mContext.getResources()).thenReturn(mResources);
-
-        assertEquals(true, mService.canUseManagedServices("d", 0, null));
-    }
-
-    @Test
-    public void testCanUseManagedServicesNoLowRamNoWatchValidPkg() {
-        when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
-        when(mActivityManager.isLowRamDevice()).thenReturn(false);
-        when(mResources.getStringArray(R.array.config_allowedManagedServicesOnLowRamDevices))
-                .thenReturn(new String[] {"a", "b", "c"});
-        when(mContext.getResources()).thenReturn(mResources);
-
-        assertEquals(true, mService.canUseManagedServices("d", 0 , null));
-    }
-
-    @Test
-    public void testCanUseManagedServicesNoLowRamWatchValidPkg() {
-        when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(true);
-        when(mActivityManager.isLowRamDevice()).thenReturn(false);
-        when(mResources.getStringArray(R.array.config_allowedManagedServicesOnLowRamDevices))
-                .thenReturn(new String[] {"a", "b", "c"});
-        when(mContext.getResources()).thenReturn(mResources);
-
+    public void testCanUseManagedServicesNoValidPkg() {
         assertEquals(true, mService.canUseManagedServices("d", 0, null));
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java
index 0ea53fa..d765042 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationTest.java
@@ -22,9 +22,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
@@ -32,15 +30,12 @@
 import android.app.PendingIntent;
 import android.app.Person;
 import android.app.RemoteInput;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.Typeface;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
-import android.os.Build;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.StyleSpan;
@@ -74,91 +69,13 @@
     }
 
     @Test
-    public void testStripsExtendersInLowRamModeNoWhitelistNoTv() {
+    public void testDoesNotStripsExtenders() {
         Notification.Builder nb = new Notification.Builder(mContext, "channel");
         nb.extend(new Notification.CarExtender().setColor(Color.RED));
         nb.extend(new Notification.TvExtender().setChannelId("different channel"));
         nb.extend(new Notification.WearableExtender().setDismissalId("dismiss"));
         Notification before = nb.build();
-
-        // No whitelist
-        Context context = spy(getContext());
-        when(context.getResources()).thenReturn(mResources);
-        when(mResources.getStringArray(anyInt())).thenReturn(new String[0]);
-
-        Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before, true,
-                context);
-
-        assertEquals("different channel", new Notification.TvExtender(before).getChannelId());
-        assertNull(new Notification.TvExtender(after).getChannelId());
-
-        assertEquals(Color.RED, new Notification.CarExtender(before).getColor());
-        assertEquals(Notification.COLOR_DEFAULT, new Notification.CarExtender(after).getColor());
-
-        assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId());
-        assertNull(new Notification.WearableExtender(after).getDismissalId());
-    }
-
-    @Test
-    public void testStripsExtendersInLowRamModeHasWhitelist() {
-        Notification.Builder nb = new Notification.Builder(mContext, "channel");
-        nb.extend(new Notification.CarExtender().setColor(Color.RED));
-        nb.extend(new Notification.TvExtender().setChannelId("different channel"));
-        nb.extend(new Notification.WearableExtender().setDismissalId("dismiss"));
-        Notification before = nb.build();
-
-        // Has whitelist
-        Context context = spy(mContext);
-        when(context.getResources()).thenReturn(mResources);
-        when(mResources.getStringArray(anyInt())).thenReturn(new String[1]);
-
-        Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before, true,
-                context);
-
-        assertEquals("different channel", new Notification.TvExtender(before).getChannelId());
-        assertEquals("different channel", new Notification.TvExtender(after).getChannelId());
-
-        assertEquals(Color.RED, new Notification.CarExtender(before).getColor());
-        assertEquals(Color.RED, new Notification.CarExtender(after).getColor());
-
-        assertEquals("dismiss", new Notification.WearableExtender(before).getDismissalId());
-        assertEquals("dismiss", new Notification.WearableExtender(after).getDismissalId());
-    }
-
-    @Test
-    public void testStripsRemoteViewsInLowRamMode() {
-        Context context = spy(mContext);
-        ApplicationInfo ai = new ApplicationInfo();
-        ai.targetSdkVersion = Build.VERSION_CODES.M;
-        when(context.getApplicationInfo()).thenReturn(ai);
-
-        final Notification.BigTextStyle style = new Notification.BigTextStyle()
-                .bigText("hello")
-                .setSummaryText("And the summary");
-        Notification before = new Notification.Builder(context, "channel")
-                .setContentText("hi")
-                .setStyle(style)
-                .build();
-
-        Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before, true,
-                mContext);
-        assertNotNull(before.contentView);
-        assertNotNull(before.bigContentView);
-        assertNotNull(before.headsUpContentView);
-        assertNull(after.contentView);
-        assertNull(after.bigContentView);
-        assertNull(after.headsUpContentView);
-    }
-
-    @Test
-    public void testDoesNotStripsExtendersInNormalRamMode() {
-        Notification.Builder nb = new Notification.Builder(mContext, "channel");
-        nb.extend(new Notification.CarExtender().setColor(Color.RED));
-        nb.extend(new Notification.TvExtender().setChannelId("different channel"));
-        nb.extend(new Notification.WearableExtender().setDismissalId("dismiss"));
-        Notification before = nb.build();
-        Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before, false,
-                mContext);
+        Notification after = Notification.Builder.maybeCloneStrippedForDelivery(before);
 
         assertTrue(before == after);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index fa182d6..c110a0c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -372,7 +372,7 @@
         doReturn(mMockPackageManager).when(mService).getPackageManagerInternalLocked();
         doReturn(false).when(mMockPackageManager).isInstantAppInstallerComponent(any());
         doReturn(null).when(mMockPackageManager).resolveIntent(any(), any(), anyInt(), anyInt(),
-                anyBoolean(), anyInt());
+                anyInt(), anyBoolean(), anyInt());
 
         // Never review permissions
         doReturn(false).when(mMockPackageManager).isPermissionsReviewRequired(any(), anyInt());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 70e5ee7..683fca4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -209,6 +209,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 149760957)
     public void testSizeCompatBounds() {
         // Disable the real configuration resolving because we only simulate partial flow.
         // TODO: Have test use full flow.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
index 8ac1d24..cc9173a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -40,6 +40,8 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.SurfaceControl;
 
+import androidx.test.filters.FlakyTest;
+
 import org.hamcrest.CustomTypeSafeMatcher;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
@@ -65,6 +67,7 @@
     private TestWindowManagerPolicy mPolicy = new TestWindowManagerPolicy(null, null);
 
     @Test
+    @FlakyTest(bugId = 149760939)
     public void testBuilder() {
         WindowManagerService wms = mSystemServices.getWindowManagerService();
         DisplayArea.Root root = new SurfacelessDisplayAreaRoot(wms);
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 c19312d..ba57745 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -599,6 +599,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 149760800)
     public void layoutWindowLw_withLongEdgeDisplayCutout() {
         addLongEdgeDisplayCutout();
 
@@ -618,6 +619,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 149760800)
     public void layoutWindowLw_withLongEdgeDisplayCutout_never() {
         addLongEdgeDisplayCutout();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index eda1fb8..b5663bd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -255,4 +255,9 @@
             int priority) {
         return this;
     }
+
+    @Override
+    public SurfaceControl.Transaction unsetColor(SurfaceControl sc) {
+        return this;
+    }
 }
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 8ad7505..55d12db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -137,6 +137,7 @@
                         }
                         throw t;
                     }
+                    if (throwable != null) throw throwable;
                 }
             }
         };
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRuleTest.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRuleTest.java
new file mode 100644
index 0000000..4056c71
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRuleTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.wm;
+
+import static junit.framework.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runners.model.Statement;
+
+import java.io.IOException;
+
+@Presubmit
+public class SystemServicesTestRuleTest {
+    @Rule
+    public ExpectedException mExpectedException = ExpectedException.none();
+
+    @Test
+    public void testRule_rethrows_unchecked_exceptions() throws Throwable {
+        final SystemServicesTestRule mWmsRule = new SystemServicesTestRule();
+        Statement statement = new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                throw new RuntimeException("A failing test!");
+            }
+        };
+        mExpectedException.expect(RuntimeException.class);
+        mWmsRule.apply(statement, null /* Description*/).evaluate();
+    }
+
+    @Test
+    public void testRule_rethrows_checked_exceptions() throws Throwable {
+        final SystemServicesTestRule mWmsRule = new SystemServicesTestRule();
+        Statement statement = new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                throw new IOException("A failing test!");
+            }
+        };
+        mExpectedException.expect(IOException.class);
+        mWmsRule.apply(statement, null /* Description*/).evaluate();
+    }
+
+    @Test
+    public void testRule_ranSuccessfully() throws Throwable {
+        final boolean[] testRan = {false};
+        final SystemServicesTestRule mWmsRule = new SystemServicesTestRule();
+        Statement statement = new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                testRan[0] = true;
+            }
+        };
+        mWmsRule.apply(statement, null /* Description*/).evaluate();
+        assertTrue(testRan[0]);
+    }
+}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index c51a852..795de57 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3391,6 +3391,25 @@
             "subscription_group_uuid_string";
 
     /**
+     * Data switch validation minimal gap time, in milliseconds.
+     *
+     * Which means, if the same subscription on the same network (based on MCC+MNC+TAC+subId)
+     * was recently validated (within this time gap), and Telephony receives a request to switch to
+     * it again, Telephony will skip the validation part and switch to it as soon as connection
+     * is setup, as if it's already validated.
+     *
+     * If the network was validated within the gap but the latest validation result is false, the
+     * validation will not be skipped.
+     *
+     * If not set or set to 0, validation will never be skipped.
+     * The max acceptable value of this config is 24 hours.
+     *
+     * @hide
+     */
+    public static final String KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG =
+            "data_switch_validation_min_gap_LONG";
+
+    /**
     * A boolean property indicating whether this subscription should be managed as an opportunistic
     * subscription.
     *
@@ -4339,6 +4358,7 @@
         sDefaults.putAll(Wifi.getDefaults());
         sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false);
         sDefaults.putBoolean(KEY_SHOW_FORWARDED_NUMBER_BOOL, false);
+        sDefaults.putLong(KEY_DATA_SWITCH_VALIDATION_MIN_GAP_LONG, 0);
         sDefaults.putAll(Iwlan.getDefaults());
     }
 
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 957683e..0886975 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -204,6 +204,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.security.KeyStore;
 import android.system.Os;
 import android.test.mock.MockContentResolver;
 import android.text.TextUtils;
@@ -1019,7 +1020,7 @@
 
         public MockVpn(int userId) {
             super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
-                    userId);
+                    userId, mock(KeyStore.class));
         }
 
         public void setNetworkAgent(TestNetworkAgentWrapper agent) {
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index ac1c518..0e3b797 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -72,6 +72,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.security.Credentials;
 import android.security.KeyStore;
 import android.util.ArrayMap;
@@ -260,17 +261,17 @@
         assertFalse(vpn.getLockdown());
 
         // Set always-on without lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList(), mKeyStore));
         assertTrue(vpn.getAlwaysOn());
         assertFalse(vpn.getLockdown());
 
         // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList(), mKeyStore));
         assertTrue(vpn.getAlwaysOn());
         assertTrue(vpn.getLockdown());
 
         // Remove always-on configuration.
-        assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
+        assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList(), mKeyStore));
         assertFalse(vpn.getAlwaysOn());
         assertFalse(vpn.getLockdown());
     }
@@ -284,11 +285,11 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
 
         // Set always-on without lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null, mKeyStore));
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
 
         // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null, mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
             new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -297,7 +298,7 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[1]);
 
         // Switch to another app.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
             new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -316,7 +317,8 @@
         final UidRange user = UidRange.createForUser(primaryUser.id);
 
         // Set always-on with lockdown and whitelist app PKGS[2] from lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[2])));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
                 new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
                 new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
@@ -325,7 +327,8 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
 
         // Change whitelisted app to PKGS[3].
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[3])));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
                 new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
         }));
@@ -337,7 +340,8 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]);
 
         // Change the VPN app.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[3])));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Collections.singletonList(PKGS[3]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
                 new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
                 new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1)
@@ -350,7 +354,7 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
 
         // Remove the whitelist.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
                 new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
                 new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -363,7 +367,8 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[0]);
 
         // Add the whitelist.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[1])));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
                 new UidRange(user.start + PKG_UIDS[0] + 1, user.stop)
         }));
@@ -375,12 +380,13 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]);
 
         // Try whitelisting a package with a comma, should be rejected.
-        assertFalse(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList("a.b,c.d")));
+        assertFalse(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Collections.singletonList("a.b,c.d"), mKeyStore));
 
         // Pass a non-existent packages in the whitelist, they (and only they) should be ignored.
         // Whitelisted package should change from PGKS[1] to PKGS[2].
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true,
-                Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{
                 new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
                 new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -405,7 +411,7 @@
         final UidRange profile = UidRange.createForUser(tempProfile.id);
 
         // Set lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
             new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -499,22 +505,22 @@
                 .thenReturn(Collections.singletonList(resInfo));
 
         // null package name should return false
-        assertFalse(vpn.isAlwaysOnPackageSupported(null));
+        assertFalse(vpn.isAlwaysOnPackageSupported(null, mKeyStore));
 
         // Pre-N apps are not supported
         appInfo.targetSdkVersion = VERSION_CODES.M;
-        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
 
         // N+ apps are supported by default
         appInfo.targetSdkVersion = VERSION_CODES.N;
-        assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+        assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
 
         // Apps that opt out explicitly are not supported
         appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
         Bundle metaData = new Bundle();
         metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false);
         svcInfo.metaData = metaData;
-        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
     }
 
     @Test
@@ -531,7 +537,7 @@
                 .cancelAsUser(anyString(), anyInt(), eq(userHandle));
 
         // Start showing a notification for disconnected once always-on.
-        vpn.setAlwaysOnPackage(PKGS[0], false, null);
+        vpn.setAlwaysOnPackage(PKGS[0], false, null, mKeyStore);
         order.verify(mNotificationManager)
                 .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
 
@@ -545,7 +551,7 @@
                 .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
 
         // Notification should be cleared after unsetting always-on package.
-        vpn.setAlwaysOnPackage(null, false, null);
+        vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
         order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
     }
 
@@ -920,12 +926,48 @@
                         eq(AppOpsManager.MODE_IGNORED));
     }
 
+    private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
+        assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null, mKeyStore));
+
+        verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+        verify(mAppOps).setMode(
+                eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG),
+                eq(AppOpsManager.MODE_ALLOWED));
+
+        verify(mSystemServices).settingsSecurePutStringForUser(
+                eq(Settings.Secure.ALWAYS_ON_VPN_APP), eq(TEST_VPN_PKG), eq(primaryUser.id));
+        verify(mSystemServices).settingsSecurePutIntForUser(
+                eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN), eq(lockdownEnabled ? 1 : 0),
+                eq(primaryUser.id));
+        verify(mSystemServices).settingsSecurePutStringForUser(
+                eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST), eq(""), eq(primaryUser.id));
+    }
+
+    @Test
+    public void testSetAndStartAlwaysOnVpn() throws Exception {
+        final Vpn vpn = createVpn(primaryUser.id);
+        setMockedUsers(primaryUser);
+
+        // UID checks must return a different UID; otherwise it'll be treated as already prepared.
+        final int uid = Process.myUid() + 1;
+        when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
+                .thenReturn(uid);
+        when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(mVpnProfile.encode());
+
+        setAndVerifyAlwaysOnPackage(vpn, uid, false);
+        assertTrue(vpn.startAlwaysOnVpn(mKeyStore));
+
+        // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
+        // a subsequent CL.
+    }
+
     /**
      * Mock some methods of vpn object.
      */
     private Vpn createVpn(@UserIdInt int userId) {
         return new Vpn(Looper.myLooper(), mContext, mNetService,
-                userId, mSystemServices, mIkev2SessionCreator);
+                userId, mKeyStore, mSystemServices, mIkev2SessionCreator);
     }
 
     private static void assertBlocked(Vpn vpn, int... uids) {
diff --git a/tools/stats_log_api_gen/Android.bp b/tools/stats_log_api_gen/Android.bp
index a251c05..e566d27 100644
--- a/tools/stats_log_api_gen/Android.bp
+++ b/tools/stats_log_api_gen/Android.bp
@@ -123,5 +123,12 @@
         "libcutils",
     ],
     static_libs: ["libstatssocket"],
+
+    apex_available: [
+        "//apex_available:platform",
+        //TODO(b/149781190): Remove this once statsd no longer depends on libstatslog
+        "com.android.os.statsd",
+        "test_com.android.os.statsd",
+    ],
 }