Merge "Keep track of commit times for each blob." into rvc-dev
diff --git a/Android.bp b/Android.bp
index 874d76f..03a6af5 100644
--- a/Android.bp
+++ b/Android.bp
@@ -339,9 +339,7 @@
             "sax/java",
             "telecomm/java",
 
-            // TODO(b/148660295): remove this
-            "apex/media/framework/java",
-
+            "apex/media/aidl/stable",
             // TODO(b/147699819): remove this
             "telephony/java",
         ],
@@ -911,6 +909,7 @@
 filegroup {
     name: "incremental_aidl",
     srcs: [
+        "core/java/android/os/incremental/IIncrementalServiceConnector.aidl",
         "core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl",
     ],
     path: "core/java",
diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
index 73b4a19..836e6b6 100644
--- a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
@@ -192,6 +192,11 @@
                 Assume.assumeNoException(
                         new AssertionError("onAnimationCanceled should not be called"));
             }
+
+            @Override
+            public void onTaskAppeared(RemoteAnimationTarget app) throws RemoteException {
+                /* no-op */
+            }
         };
 
         recentsSemaphore.tryAcquire();
diff --git a/apex/Android.bp b/apex/Android.bp
index 5f418d4..67cd0d7 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -67,7 +67,7 @@
     name: "framework-module-stubs-defaults-publicapi",
     args: mainline_framework_stubs_args,
     installable: false,
-    sdk_version: "current",
+    sdk_version: "module_current",
     filter_packages: framework_packages_to_document,
     check_api: {
         current: {
@@ -86,7 +86,7 @@
     args: mainline_framework_stubs_args + priv_apps,
     libs: ["framework-annotations-lib"],
     installable: false,
-    sdk_version: "system_current",
+    sdk_version: "module_current",
     filter_packages: framework_packages_to_document,
     check_api: {
         current: {
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index b6f85b2..1ab7938 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -1364,6 +1364,19 @@
                     if (DEBUG) {
                         Slog.d(TAG, "    Keeping at WORKING_SET due to min timeout");
                     }
+                } else if (newBucket == STANDBY_BUCKET_RARE
+                        && getBucketForLocked(packageName, userId, elapsedRealtime)
+                        == STANDBY_BUCKET_RESTRICTED) {
+                    // Prediction doesn't think the app will be used anytime soon and
+                    // it's been long enough that it could just time out into restricted,
+                    // so time it out there instead. Using TIMEOUT will allow prediction
+                    // to raise the bucket when it needs to.
+                    newBucket = STANDBY_BUCKET_RESTRICTED;
+                    reason = REASON_MAIN_TIMEOUT;
+                    if (DEBUG) {
+                        Slog.d(TAG,
+                                "Prediction to RARE overridden by timeout into RESTRICTED");
+                    }
                 }
             }
 
diff --git a/apex/media/aidl/Android.bp b/apex/media/aidl/Android.bp
new file mode 100644
index 0000000..409a048
--- /dev/null
+++ b/apex/media/aidl/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright 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.
+//
+
+filegroup {
+    name: "stable-mediasession2-aidl-srcs",
+    srcs: ["stable/**/*.aidl"],
+    path: "stable",
+}
+
+filegroup {
+    name: "private-mediasession2-aidl-srcs",
+    srcs: ["private/**/I*.aidl"],
+    path: "private",
+}
+
+filegroup {
+    name: "mediasession2-aidl-srcs",
+    srcs: [
+        ":private-mediasession2-aidl-srcs",
+        ":stable-mediasession2-aidl-srcs",
+    ],
+}
diff --git a/apex/media/framework/java/android/media/Controller2Link.aidl b/apex/media/aidl/private/android/media/Controller2Link.aidl
similarity index 100%
rename from apex/media/framework/java/android/media/Controller2Link.aidl
rename to apex/media/aidl/private/android/media/Controller2Link.aidl
diff --git a/apex/media/framework/java/android/media/IMediaController2.aidl b/apex/media/aidl/private/android/media/IMediaController2.aidl
similarity index 100%
rename from apex/media/framework/java/android/media/IMediaController2.aidl
rename to apex/media/aidl/private/android/media/IMediaController2.aidl
diff --git a/apex/media/framework/java/android/media/IMediaSession2.aidl b/apex/media/aidl/private/android/media/IMediaSession2.aidl
similarity index 100%
rename from apex/media/framework/java/android/media/IMediaSession2.aidl
rename to apex/media/aidl/private/android/media/IMediaSession2.aidl
diff --git a/apex/media/framework/java/android/media/IMediaSession2Service.aidl b/apex/media/aidl/private/android/media/IMediaSession2Service.aidl
similarity index 100%
rename from apex/media/framework/java/android/media/IMediaSession2Service.aidl
rename to apex/media/aidl/private/android/media/IMediaSession2Service.aidl
diff --git a/apex/media/framework/java/android/media/Session2Command.aidl b/apex/media/aidl/private/android/media/Session2Command.aidl
similarity index 100%
rename from apex/media/framework/java/android/media/Session2Command.aidl
rename to apex/media/aidl/private/android/media/Session2Command.aidl
diff --git a/apex/media/framework/java/android/media/Session2Token.aidl b/apex/media/aidl/stable/android/media/Session2Token.aidl
similarity index 100%
rename from apex/media/framework/java/android/media/Session2Token.aidl
rename to apex/media/aidl/stable/android/media/Session2Token.aidl
diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp
index 99e82e7..34fe228 100644
--- a/apex/media/framework/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -55,17 +55,15 @@
     name: "updatable-media-srcs",
     srcs: [
         ":mediaparser-srcs",
-        ":mediasession2-srcs",
+        ":mediasession2-java-srcs",
+        ":mediasession2-aidl-srcs",
     ],
 }
 
 filegroup {
-    name: "mediasession2-srcs",
+    name: "mediasession2-java-srcs",
     srcs: [
         "java/android/media/Controller2Link.java",
-        "java/android/media/IMediaController2.aidl",
-        "java/android/media/IMediaSession2.aidl",
-        "java/android/media/IMediaSession2Service.aidl",
         "java/android/media/MediaConstants.java",
         "java/android/media/MediaController2.java",
         "java/android/media/MediaSession2.java",
@@ -83,7 +81,7 @@
     srcs: [
         "java/android/media/MediaParser.java"
     ],
-    path: "java"
+    path: "java",
 }
 
 stubs_defaults {
@@ -94,7 +92,6 @@
         // TODO(b/135922046) remove this
         include_dirs: ["frameworks/base/core/java"],
     },
-    sdk_version: "system_current",
 }
 
 droidstubs {
diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp
index 793247e..fc9052e 100644
--- a/apex/permission/framework/Android.bp
+++ b/apex/permission/framework/Android.bp
@@ -46,12 +46,10 @@
     name: "framework-permission-stubs-defaults",
     srcs: [ ":framework-permission-sources" ],
     libs: [ "framework-annotations-lib" ],
-    sdk_version: "system_current",
 }
 
 droidstubs {
     name: "framework-permission-stubs-srcs-publicapi",
-    sdk_version: "system_current",
     defaults: [
         "framework-module-stubs-defaults-publicapi",
         "framework-permission-stubs-defaults",
@@ -60,7 +58,6 @@
 
 droidstubs {
     name: "framework-permission-stubs-srcs-systemapi",
-    sdk_version: "system_current",
     defaults: [
         "framework-module-stubs-defaults-systemapi",
         "framework-permission-stubs-defaults",
@@ -69,7 +66,6 @@
 
 droidstubs {
     name: "framework-permission-api-module_libs_api",
-    sdk_version: "system_current",
     defaults: [
         "framework-module-api-defaults-module_libs_api",
         "framework-permission-stubs-defaults",
@@ -78,7 +74,6 @@
 
 droidstubs {
     name: "framework-permission-stubs-srcs-module_libs_api",
-    sdk_version: "system_current",
     defaults: [
         "framework-module-stubs-defaults-module_libs_api",
         "framework-permission-stubs-defaults",
diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp
index 707113b..3eabb88 100644
--- a/apex/sdkextensions/framework/Android.bp
+++ b/apex/sdkextensions/framework/Android.bp
@@ -48,7 +48,6 @@
     name: "framework-sdkextensions-stubs-defaults",
     srcs: [ ":framework-sdkextensions-sources" ],
     libs: [ "framework-annotations-lib" ],
-    sdk_version: "system_current",
 }
 
 droidstubs {
diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp
index 32e13e3..15d7495 100644
--- a/apex/statsd/Android.bp
+++ b/apex/statsd/Android.bp
@@ -67,7 +67,6 @@
         "liblog",  // Has a stable abi - should not be copied into apex.
         "libstatssocket",
     ],
-    //TODO: is libc++_static correct?
     stl: "libc++_static",
     cflags: [
         "-Wall",
diff --git a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
index b94928f..5cdb324 100644
--- a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
@@ -59,9 +59,6 @@
     /** Cancel any alarm for the purpose of subscriber triggering. */
     oneway void cancelAlarmForSubscriberTriggering();
 
-    /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */
-    oneway void triggerUidSnapshot();
-
     /**
      * Ask StatsCompanionService if the given permission is allowed for a particular process
      * and user ID. statsd is incapable of doing this check itself because checkCallingPermission
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index aad5112..66e41cc 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -153,7 +153,7 @@
         }
     }
 
-    private static void informAllUidsLocked(Context context) throws RemoteException {
+    private static void informAllUids(Context context) {
         UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE);
         PackageManager pm = context.getPackageManager();
         final List<UserHandle> users = um.getUserHandles(true);
@@ -168,18 +168,26 @@
             Log.e(TAG, "Failed to create a pipe to send uid map data.", e);
             return;
         }
-        sStatsd.informAllUidData(fds[0]);
-        try {
-            fds[0].close();
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to close the read side of the pipe.", e);
-        }
-        final ParcelFileDescriptor writeFd = fds[1];
         HandlerThread backgroundThread = new HandlerThread(
                 "statsCompanionService.bg", THREAD_PRIORITY_BACKGROUND);
         backgroundThread.start();
         Handler handler = new Handler(backgroundThread.getLooper());
         handler.post(() -> {
+            IStatsd statsd = getStatsdNonblocking();
+            if (statsd == null) {
+                return;
+            }
+            try {
+                statsd.informAllUidData(fds[0]);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to send uid map to statsd");
+            }
+            try {
+                fds[0].close();
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to close the read side of the pipe.", e);
+            }
+            final ParcelFileDescriptor writeFd = fds[1];
             FileOutputStream fout = new ParcelFileDescriptor.AutoCloseOutputStream(writeFd);
             try {
                 ProtoOutputStream output = new ProtoOutputStream(fout);
@@ -188,7 +196,8 @@
                 for (UserHandle userHandle : users) {
                     List<PackageInfo> pi =
                             pm.getInstalledPackagesAsUser(PackageManager.MATCH_UNINSTALLED_PACKAGES
-                                            | PackageManager.MATCH_ANY_USER,
+                                            | PackageManager.MATCH_ANY_USER
+                                            | PackageManager.MATCH_APEX,
                                     userHandle.getIdentifier());
                     for (int j = 0; j < pi.size(); j++) {
                         if (pi.get(j).applicationInfo != null) {
@@ -319,19 +328,9 @@
     private static final class UserUpdateReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            synchronized (sStatsdLock) {
-                if (sStatsd == null) {
-                    Log.w(TAG, "Could not access statsd for UserUpdateReceiver");
-                    return;
-                }
-                try {
-                    // Pull the latest state of UID->app name, version mapping.
-                    // Needed since the new user basically has a version of every app.
-                    informAllUidsLocked(context);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Failed to inform statsd latest update of all apps", e);
-                }
-            }
+            // Pull the latest state of UID->app name, version mapping.
+            // Needed since the new user basically has a version of every app.
+            informAllUids(context);
         }
     }
 
@@ -589,21 +588,6 @@
     }
 
     @Override // Binder call
-    public void triggerUidSnapshot() {
-        StatsCompanion.enforceStatsdCallingUid();
-        synchronized (sStatsdLock) {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                informAllUidsLocked(mContext);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to trigger uid snapshot.", e);
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
-    }
-
-    @Override // Binder call
     public boolean checkPermission(String permission, int pid, int uid) {
         StatsCompanion.enforceStatsdCallingUid();
         return mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED;
@@ -707,7 +691,7 @@
             try {
                 // Pull the latest state of UID->app name, version mapping when
                 // statsd starts.
-                informAllUidsLocked(mContext);
+                informAllUids(mContext);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/apex/statsd/tests/libstatspull/Android.bp b/apex/statsd/tests/libstatspull/Android.bp
index 2d64f19..0df96e1 100644
--- a/apex/statsd/tests/libstatspull/Android.bp
+++ b/apex/statsd/tests/libstatspull/Android.bp
@@ -32,7 +32,7 @@
         "protos/**/*.proto",
         ],
     test_suites: [
-        "general-tests",
+        "device-tests",
     ],
     platform_apis: true,
     privileged: true,
diff --git a/api/current.txt b/api/current.txt
index b4db1f7..80e2d00 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -46230,7 +46230,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts();
     method public String getDefaultDialerPackage();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(String);
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1Number(android.telecom.PhoneAccountHandle);
+    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle);
     method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
     method public android.telecom.PhoneAccountHandle getSimCallManager();
diff --git a/api/test-current.txt b/api/test-current.txt
index 5de6c2a..641767c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -214,6 +214,7 @@
     field public static final String OPSTR_GPS = "android:gps";
     field public static final String OPSTR_INSTANT_APP_START_FOREGROUND = "android:instant_app_start_foreground";
     field public static final String OPSTR_LEGACY_STORAGE = "android:legacy_storage";
+    field public static final String OPSTR_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage";
     field public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels";
     field public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone";
     field public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells";
@@ -4860,7 +4861,9 @@
   }
 
   public final class SurfaceControl implements android.os.Parcelable {
+    ctor public SurfaceControl(@NonNull android.view.SurfaceControl);
     method public static long acquireFrameRateFlexibilityToken();
+    method public boolean isSameSurface(@NonNull android.view.SurfaceControl);
     method public static void releaseFrameRateFlexibilityToken(long);
   }
 
@@ -5213,10 +5216,10 @@
     method public void onDisplayAreaAppeared(@NonNull android.window.WindowContainerToken);
     method public void onDisplayAreaVanished(@NonNull android.window.WindowContainerToken);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void registerOrganizer(int);
+    field public static final int FEATURE_DEFAULT_TASK_CONTAINER = 1; // 0x1
     field public static final int FEATURE_ROOT = 0; // 0x0
     field public static final int FEATURE_SYSTEM_FIRST = 0; // 0x0
     field public static final int FEATURE_SYSTEM_LAST = 10000; // 0x2710
-    field public static final int FEATURE_TASK_CONTAINER = 1; // 0x1
     field public static final int FEATURE_UNDEFINED = -1; // 0xffffffff
     field public static final int FEATURE_VENDOR_FIRST = 10001; // 0x2711
     field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2
diff --git a/cmds/idmap2/TEST_MAPPING b/cmds/idmap2/TEST_MAPPING
index 26ccf03..9e0fb84 100644
--- a/cmds/idmap2/TEST_MAPPING
+++ b/cmds/idmap2/TEST_MAPPING
@@ -3,5 +3,10 @@
     {
       "name" : "idmap2_tests"
     }
+  ],
+  "imports": [
+    {
+      "path": "frameworks/base/services/core/java/com/android/server/om"
+    }
   ]
 }
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index e55ea6c..75fc7f7 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -149,15 +149,21 @@
     return error(idmap.GetErrorMessage());
   }
 
+  // idmap files are mapped with mmap in libandroidfw. Deleting and recreating the idmap guarantees
+  // that existing memory maps will continue to be valid and unaffected.
+  unlink(idmap_path.c_str());
+
   umask(kIdmapFilePermissionMask);
   std::ofstream fout(idmap_path);
   if (fout.fail()) {
     return error("failed to open idmap path " + idmap_path);
   }
+
   BinaryStreamVisitor visitor(fout);
   (*idmap)->accept(&visitor);
   fout.close();
   if (fout.fail()) {
+    unlink(idmap_path.c_str());
     return error("failed to write to idmap path " + idmap_path);
   }
 
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index d3d7e1d..65061d0 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -113,17 +113,18 @@
     static_libs: [
         "libbase",
         "libcutils",
+        "libgtest_prod",
         "libprotoutil",
         "libstatsmetadata",
         "libstatslog_statsd",
         "libsysutils",
         "libutils",
+        "statsd-aidl-ndk_platform",
     ],
     shared_libs: [
         "libbinder_ndk",
         "libincident",
         "liblog",
-        "statsd-aidl-ndk_platform",
     ],
 }
 
@@ -268,10 +269,11 @@
 
     proto: {
         type: "lite",
+        static: true,
     },
+    stl: "libc++_static",
 
     shared_libs: [
-        "libgtest_prod",
         "libstatssocket",
     ],
 
diff --git a/cmds/statsd/benchmark/filter_value_benchmark.cpp b/cmds/statsd/benchmark/filter_value_benchmark.cpp
index 28bf21a..743ccc4 100644
--- a/cmds/statsd/benchmark/filter_value_benchmark.cpp
+++ b/cmds/statsd/benchmark/filter_value_benchmark.cpp
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 #include <vector>
-#include "benchmark/benchmark.h"
+
 #include "FieldValue.h"
 #include "HashableDimensionKey.h"
+#include "benchmark/benchmark.h"
 #include "logd/LogEvent.h"
-#include "stats_log_util.h"
+#include "metric_util.h"
 #include "stats_event.h"
+#include "stats_log_util.h"
 
 namespace android {
 namespace os {
@@ -34,24 +36,13 @@
 
     std::vector<int> attributionUids = {100, 100};
     std::vector<string> attributionTags = {"LOCATION", "LOCATION"};
+    writeAttribution(statsEvent, attributionUids, attributionTags);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
     AStatsEvent_writeFloat(statsEvent, 3.2f);
     AStatsEvent_writeString(statsEvent, "LOCATION");
     AStatsEvent_writeInt64(statsEvent, 990);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    event->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, event);
 
     field_matcher->set_field(1);
     auto child = field_matcher->add_child();
diff --git a/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
index c7d01cc..7a45565 100644
--- a/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
+++ b/cmds/statsd/benchmark/get_dimensions_for_condition_benchmark.cpp
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 #include <vector>
-#include "benchmark/benchmark.h"
+
 #include "FieldValue.h"
 #include "HashableDimensionKey.h"
+#include "benchmark/benchmark.h"
 #include "logd/LogEvent.h"
-#include "stats_log_util.h"
+#include "metric_util.h"
 #include "stats_event.h"
+#include "stats_log_util.h"
 
 namespace android {
 namespace os {
@@ -34,24 +36,13 @@
 
     std::vector<int> attributionUids = {100, 100};
     std::vector<string> attributionTags = {"LOCATION", "LOCATION"};
+    writeAttribution(statsEvent, attributionUids, attributionTags);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
     AStatsEvent_writeFloat(statsEvent, 3.2f);
     AStatsEvent_writeString(statsEvent, "LOCATION");
     AStatsEvent_writeInt64(statsEvent, 990);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    event->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, event);
 
     link->conditionId = 1;
 
diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp
index 482d66f..89fd3d9 100644
--- a/cmds/statsd/benchmark/metric_util.cpp
+++ b/cmds/statsd/benchmark/metric_util.cpp
@@ -247,21 +247,37 @@
     return dimensions;
 }
 
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+                      const vector<string>& attributionTags) {
+    vector<const char*> cTags(attributionTags.size());
+    for (int i = 0; i < cTags.size(); i++) {
+        cTags[i] = attributionTags[i].c_str();
+    }
+
+    AStatsEvent_writeAttributionChain(statsEvent,
+                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
+                                      cTags.data(), attributionUids.size());
+}
+
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) {
+    AStatsEvent_build(statsEvent);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    logEvent->parseBuffer(buf, size);
+
+    AStatsEvent_release(statsEvent);
+}
+
 std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
         uint64_t timestampNs, const android::view::DisplayStateEnum state) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -272,24 +288,12 @@
     AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeString(statsEvent, jobName.c_str());
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -319,24 +323,12 @@
     AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeString(statsEvent, name.c_str());
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
diff --git a/cmds/statsd/benchmark/metric_util.h b/cmds/statsd/benchmark/metric_util.h
index c5fcf7c..3efaa85 100644
--- a/cmds/statsd/benchmark/metric_util.h
+++ b/cmds/statsd/benchmark/metric_util.h
@@ -18,6 +18,7 @@
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "src/StatsLogProcessor.h"
 #include "src/logd/LogEvent.h"
+#include "stats_event.h"
 #include "statslog.h"
 
 namespace android {
@@ -92,6 +93,11 @@
 FieldMatcher CreateAttributionUidDimensions(const int atomId,
                                             const std::vector<Position>& positions);
 
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+                      const vector<string>& attributionTags);
+
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent);
+
 // Create log event for screen state changed.
 std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
         uint64_t timestampNs, const android::view::DisplayStateEnum state);
diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp
index 4385964..cfc1de4 100644
--- a/cmds/statsd/src/FieldValue.cpp
+++ b/cmds/statsd/src/FieldValue.cpp
@@ -120,9 +120,9 @@
 }
 
 int32_t getUidIfExists(const FieldValue& value) {
-    // the field is uid field if the field is the uid field in attribution node or marked as
-    // is_uid in atoms.proto
-    bool isUid = isAttributionUidField(value) || isUidField(value.mField, value.mValue);
+    // the field is uid field if the field is the uid field in attribution node
+    // or annotated as such in the atom
+    bool isUid = isAttributionUidField(value) || isUidField(value);
     return isUid ? value.mValue.int_value : -1;
 }
 
@@ -134,16 +134,8 @@
     return false;
 }
 
-bool isUidField(const Field& field, const Value& value) {
-    auto it = android::util::AtomsInfo::kAtomsWithUidField.find(field.getTag());
-
-    if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
-        int uidField = it->second;  // uidField is the field number in proto
-        return field.getDepth() == 0 && field.getPosAtDepth(0) == uidField &&
-               value.getType() == INT;
-    }
-
-    return false;
+bool isUidField(const FieldValue& fieldValue) {
+    return fieldValue.mAnnotations.isUidField();
 }
 
 Value::Value(const Value& from) {
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 3536e5a..92e09ea 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -367,7 +367,8 @@
     enum {
         NESTED_POS = 0x0,
         PRIMARY_POS = 0x1,
-        EXCLUSIVE_POS = 0x2
+        EXCLUSIVE_POS = 0x2,
+        UID_POS = 0x3
     };
 
     inline void setNested(bool nested) { setBitmaskAtPos(NESTED_POS, nested); }
@@ -376,6 +377,8 @@
 
     inline void setExclusiveState(bool exclusive) { setBitmaskAtPos(EXCLUSIVE_POS, exclusive); }
 
+    inline void setUidField(bool isUid) { setBitmaskAtPos(UID_POS, isUid); }
+
     inline void setResetState(int resetState) { mResetState = resetState; }
 
     // Default value = false
@@ -387,6 +390,9 @@
     // Default value = false
     inline bool isExclusiveState() const { return getValueFromBitmask(EXCLUSIVE_POS); }
 
+    // Default value = false
+    inline bool isUidField() const { return getValueFromBitmask(UID_POS); }
+
     // If a reset state is not sent in the StatsEvent, returns -1. Note that a
     // reset satate is only sent if and only if a reset should be triggered.
     inline int getResetState() const { return mResetState; }
@@ -402,7 +408,7 @@
     }
 
     // This is a bitmask over all annotations stored in boolean form. Because
-    // there are only 3 booleans, just one byte is required.
+    // there are only 4 booleans, just one byte is required.
     uint8_t mBooleanBitmask = 0;
 
     int mResetState = -1;
@@ -449,7 +455,7 @@
 void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* output);
 
 bool isAttributionUidField(const Field& field, const Value& value);
-bool isUidField(const Field& field, const Value& value);
+bool isUidField(const FieldValue& fieldValue);
 
 bool equalDimensions(const std::vector<Matcher>& dimension_a,
                      const std::vector<Matcher>& dimension_b);
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 982a63e..325cbc7 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -138,13 +138,6 @@
     }
 }
 
-void updateUid(Value* value, int hostUid) {
-    int uid = value->int_value;
-    if (uid != hostUid) {
-        value->setInt(hostUid);
-    }
-}
-
 void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const {
     if (android::util::AtomsInfo::kAtomsWithAttributionChain.find(event->GetTagId()) !=
         android::util::AtomsInfo::kAtomsWithAttributionChain.end()) {
@@ -154,22 +147,15 @@
             }
             if (isAttributionUidField(value)) {
                 const int hostUid = mUidMap->getHostUidOrSelf(value.mValue.int_value);
-                updateUid(&value.mValue, hostUid);
+                value.mValue.setInt(hostUid);
             }
         }
     } else {
-        auto it = android::util::AtomsInfo::kAtomsWithUidField.find(event->GetTagId());
-        if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
-            int uidField = it->second;  // uidField is the field number in proto,
-                                        // starting from 1
-            if (uidField > 0 && (int)event->getValues().size() >= uidField &&
-                (event->getValues())[uidField - 1].mValue.getType() == INT) {
-                Value& value = (*event->getMutableValues())[uidField - 1].mValue;
-                const int hostUid = mUidMap->getHostUidOrSelf(value.int_value);
-                updateUid(&value, hostUid);
-            } else {
-                ALOGE("Malformed log, uid not found. %s", event->ToString().c_str());
-            }
+        int uidFieldIndex = event->getUidFieldIndex();
+        if (uidFieldIndex != -1) {
+           Value& value = (*event->getMutableValues())[uidFieldIndex].mValue;
+           const int hostUid = mUidMap->getHostUidOrSelf(value.int_value);
+           value.setInt(hostUid);
         }
     }
 }
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index bd15264..453ddeb 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -384,12 +384,12 @@
         PerfettoUploaded perfetto_uploaded = 229 [(module) = "perfetto"];
         VmsClientConnectionStateChanged vms_client_connection_state_changed =
                 230 [(module) = "car"];
-        MediaProviderScanEvent media_provider_scan_event = 233 [(module) = "mediaprovider"];
-        MediaProviderDeletionEvent media_provider_deletion_event = 234 [(module) = "mediaprovider"];
-        MediaProviderPermissionEvent media_provider_permission_event =
+        MediaProviderScanOccurred media_provider_scan_occurred = 233 [(module) = "mediaprovider"];
+        MediaContentDeleted media_content_deleted = 234 [(module) = "mediaprovider"];
+        MediaProviderPermissionRequested media_provider_permission_requested =
             235 [(module) = "mediaprovider"];
-        MediaProviderSchemaChange media_provider_schema_change = 236 [(module) = "mediaprovider"];
-        MediaProviderIdleMaintenance media_provider_idle_maintenance =
+        MediaProviderSchemaChanged media_provider_schema_changed = 236 [(module) = "mediaprovider"];
+        MediaProviderIdleMaintenanceFinished media_provider_idle_maintenance_finished =
             237 [(module) = "mediaprovider"];
         RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238 [(module) = "framework"];
         BootTimeEventDuration boot_time_event_duration_reported = 239 [(module) = "framework"];
@@ -418,7 +418,9 @@
         AppStandbyBucketChanged app_standby_bucket_changed = 258 [(module) = "framework"];
         SharesheetStarted sharesheet_started = 259 [(module) = "framework"];
         RankingSelected ranking_selected = 260 [(module) = "framework"];
-        TvSettingsUIInteracted tvsettings_ui_interacted = 261;
+        TvSettingsUIInteracted tvsettings_ui_interacted = 261 [(module) = "tv_settings"];
+        LauncherStaticLayout launcher_snapshot = 262 [(module) = "sysui"];
+        PackageInstallerV2Reported package_installer_v2_reported = 263 [(module) = "framework"];
         SdkExtensionStatus sdk_extension_status = 354;
     }
 
@@ -2979,14 +2981,98 @@
     optional int32 duration_millis = 7;
 }
 
+/**
+ * Logs when Launcher (HomeScreen) UI has changed or was interacted.
+ *
+ * Logged from:
+ *   packages/apps/Launcher3
+ */
 message LauncherUIChanged {
-    optional android.stats.launcher.LauncherAction action = 1;
+    optional android.stats.launcher.LauncherAction action = 1 [deprecated = true];
     optional android.stats.launcher.LauncherState src_state = 2;
     optional android.stats.launcher.LauncherState dst_state = 3;
-    optional android.stats.launcher.LauncherExtension extension = 4 [(log_mode) = MODE_BYTES];
-    optional bool is_swipe_up_enabled = 5;
+    optional android.stats.launcher.LauncherExtension extension = 4 [(log_mode) = MODE_BYTES, deprecated = true];
+    optional bool is_swipe_up_enabled = 5 [deprecated = true];
+
+    // The event id (e.g., app launch, drag and drop, long press)
+    optional int32 event_id = 6;
+    // The event's source or target id (e.g., icon, task, button)
+    optional int32 target_id = 7;
+    // If the target needs to be tracked, use this id field
+    optional int32 instance_id = 8;
+    optional int32 uid = 9 [(is_uid) = true];
+    optional string package_name = 10;
+    optional string component_name = 11;
+
+    // (x, y) coordinate and the index information of the target on the container
+    optional int32 grid_x = 12;
+    optional int32 grid_y = 13;
+    optional int32 page_id = 14;
+
+    // e.g., folder icon's (x, y) location and index information on the workspace
+    optional int32 grid_x_parent = 15;
+    optional int32 grid_y_parent = 16;
+    optional int32 page_id_parent = 17;
+
+    // e.g., SEARCHBOX_ALLAPPS, FOLDER_WORKSPACE
+    optional int32 hierarchy = 18;
+
+    optional bool is_work_profile = 19;
+
+    // Used to store the predicted rank of the target
+    optional int32 rank = 20;
+
+    // e.g., folderLabelState can be captured in the following two fields
+    optional int32 from_state = 21;
+    optional int32 to_state = 22;
+
+    // e.g., autofilled or suggested texts that are not user entered
+    optional string edittext = 23;
 }
 
+/**
+ * Used for snapshot of the HomeScreen UI elements
+ *
+ * Logged from:
+ *   packages/apps/Launcher3
+ */
+message LauncherStaticLayout {
+    // The event id (e.g., snapshot, drag and drop)
+    optional int32 event_id = 1;
+    // The event's source or target id (e.g., icon, shortcut, widget)
+    optional int32 target_id = 2;
+    // If the target needs to be tracked, use this id field
+    optional int32 instance_id = 3;
+    optional int32 uid = 4 [(is_uid) = true];
+    optional string package_name = 5;
+    optional string component_name = 6;
+
+    // (x, y) coordinate and the index information of the target on the container
+    optional int32 grid_x = 7;
+    optional int32 grid_y = 8;
+    optional int32 page_id = 9;
+
+    // e.g., folder icon's (x, y) location and index information on the workspace
+    optional int32 grid_x_parent = 10;
+    optional int32 grid_y_parent = 11;
+    optional int32 page_id_parent = 12;
+
+    // e.g., WORKSPACE, HOTSEAT, FOLDER_WORKSPACE, FOLDER_HOTSEAT
+    optional int32 hierarchy = 13;
+
+    optional bool is_work_profile = 14;
+
+    // e.g., PIN, WIDGET TRAY, APPS TRAY, PREDICTION
+    optional int32 origin = 15;
+}
+
+/**
+ * Logs when Wallpaper or ThemePicker UI has changed.
+ *
+ * Logged from:
+ *   packages/apps/ThemePicker
+ *   packages/apps/WallpaperPicker2
+ */
 message StyleUIChanged {
     optional android.stats.style.Action action = 1;
     optional int32 color_package_hash = 2;
@@ -4370,7 +4456,7 @@
  * Logged from:
  *   packages/providers/MediaProvider/src/com/android/providers/media/scan/ModernMediaScanner.java
  */
-message MediaProviderScanEvent {
+message MediaProviderScanOccurred {
     enum Reason {
         // Scan triggered due to unknown reason
         UNKNOWN = 0;
@@ -4404,15 +4490,13 @@
  * Logged from:
  *   packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
  */
-message MediaProviderDeletionEvent {
+message MediaContentDeleted {
     // Volume type that this event pertains to
     optional android.stats.mediaprovider.VolumeType volume_type = 1;
-    // Device timestamp when this deletion event occurred
-    optional int64 timestamp_millis = 2;
-    // App that requested deletion
-    optional string package_name = 3;
+    // UID of app that requested deletion
+    optional int32 uid = 2 [(is_uid) = true];
     // Number of items that were deleted
-    optional int32 item_count = 4;
+    optional int32 item_count = 3;
 }
 
 /**
@@ -4421,7 +4505,7 @@
  * Logged from:
  *   packages/providers/MediaProvider/src/com/android/providers/media/PermissionActivity.java
  */
-message MediaProviderPermissionEvent {
+message MediaProviderPermissionRequested {
     enum Result {
         UNKNOWN = 0;
         USER_GRANTED = 1;
@@ -4433,14 +4517,12 @@
 
     // Volume type that this event pertains to
     optional android.stats.mediaprovider.VolumeType volume_type = 1;
-    // Device timestamp when this permission event occurred
-    optional int64 timestamp_millis = 2;
-    // App that requested permission
-    optional string package_name = 3;
+    // UID of app that requested permission
+    optional int32 uid = 2 [(is_uid) = true];
     // Number of items that were requested
-    optional int32 item_count = 4;
+    optional int32 item_count = 3;
     // Result of this request
-    optional Result result = 5;
+    optional Result result = 4;
 }
 
 /**
@@ -4449,7 +4531,7 @@
  * Logged from:
  *   packages/providers/MediaProvider/src/com/android/providers/media/DatabaseHelper.java
  */
-message MediaProviderSchemaChange {
+message MediaProviderSchemaChanged {
     // Volume type that this event pertains to
     optional android.stats.mediaprovider.VolumeType volume_type = 1;
     // Old database version code
@@ -4468,7 +4550,7 @@
  * Logged from:
  *   packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
  */
-message MediaProviderIdleMaintenance {
+message MediaProviderIdleMaintenanceFinished {
     // Volume type that this event pertains to
     optional android.stats.mediaprovider.VolumeType volume_type = 1;
 
@@ -9223,6 +9305,25 @@
 }
 
 /**
+ * Logs information about a package installation using package installer V2 APIs.
+ *
+ * Logged from:
+ *      frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java
+ */
+message PackageInstallerV2Reported {
+    // Whether this installation uses Incremental File System
+    optional bool is_incremental = 1;
+    // Name of the package that is intended to be installed
+    optional string package_name = 2;
+    // The duration between when the install was requested to when the install has completed
+    optional int64 duration_millis = 3;
+    // Installation result in final integer, which are SystemApi's.
+    // Return_code 1 indicates success.
+    // For full list, see frameworks/base/core/java/android/content/pm/PackageManager.java
+    optional int32 return_code  = 4;
+}
+
+/**
  * Logs settings provider values.
  *
  * Use DeviceConfig.getProperties to get a list Setting key, query the data from content provider,
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 6d9c644..bbae3fe 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -43,20 +43,23 @@
 using std::unique_ptr;
 
 struct ConfigReceiverDeathCookie {
-    ConfigReceiverDeathCookie(sp<ConfigManager> configManager, const ConfigKey& configKey,
-                              const shared_ptr<IPendingIntentRef>& pir):
-        mConfigManager(configManager),
-        mConfigKey(configKey),
-        mPir(pir) {}
+    ConfigReceiverDeathCookie(const wp<ConfigManager>& configManager, const ConfigKey& configKey,
+                              const shared_ptr<IPendingIntentRef>& pir) :
+            mConfigManager(configManager), mConfigKey(configKey), mPir(pir) {
+    }
 
-    sp<ConfigManager> mConfigManager;
+    wp<ConfigManager> mConfigManager;
     ConfigKey mConfigKey;
     shared_ptr<IPendingIntentRef> mPir;
 };
 
 void ConfigManager::configReceiverDied(void* cookie) {
     auto cookie_ = static_cast<ConfigReceiverDeathCookie*>(cookie);
-    sp<ConfigManager>& thiz = cookie_->mConfigManager;
+    sp<ConfigManager> thiz = cookie_->mConfigManager.promote();
+    if (!thiz) {
+        return;
+    }
+
     ConfigKey& configKey = cookie_->mConfigKey;
     shared_ptr<IPendingIntentRef>& pir = cookie_->mPir;
 
@@ -74,20 +77,23 @@
 }
 
 struct ActiveConfigChangedReceiverDeathCookie {
-    ActiveConfigChangedReceiverDeathCookie(sp<ConfigManager> configManager, const int uid,
-                                           const shared_ptr<IPendingIntentRef>& pir):
-        mConfigManager(configManager),
-        mUid(uid),
-        mPir(pir) {}
+    ActiveConfigChangedReceiverDeathCookie(const wp<ConfigManager>& configManager, const int uid,
+                                           const shared_ptr<IPendingIntentRef>& pir) :
+            mConfigManager(configManager), mUid(uid), mPir(pir) {
+    }
 
-    sp<ConfigManager> mConfigManager;
+    wp<ConfigManager> mConfigManager;
     int mUid;
     shared_ptr<IPendingIntentRef> mPir;
 };
 
 void ConfigManager::activeConfigChangedReceiverDied(void* cookie) {
     auto cookie_ = static_cast<ActiveConfigChangedReceiverDeathCookie*>(cookie);
-    sp<ConfigManager>& thiz = cookie_->mConfigManager;
+    sp<ConfigManager> thiz = cookie_->mConfigManager.promote();
+    if (!thiz) {
+        return;
+    }
+
     int uid = cookie_->mUid;
     shared_ptr<IPendingIntentRef>& pir = cookie_->mPir;
 
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 79a7e8d..ebe9610 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -44,19 +44,23 @@
 // Stores the puller as a wp to avoid holding a reference in case it is unregistered and
 // pullAtomCallbackDied is never called.
 struct PullAtomCallbackDeathCookie {
-    PullAtomCallbackDeathCookie(sp<StatsPullerManager> pullerManager, const PullerKey& pullerKey,
-                                const wp<StatsPuller>& puller)
-        : mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) {
+    PullAtomCallbackDeathCookie(const wp<StatsPullerManager>& pullerManager,
+                                const PullerKey& pullerKey, const wp<StatsPuller>& puller) :
+            mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) {
     }
 
-    sp<StatsPullerManager> mPullerManager;
+    wp<StatsPullerManager> mPullerManager;
     PullerKey mPullerKey;
     wp<StatsPuller> mPuller;
 };
 
 void StatsPullerManager::pullAtomCallbackDied(void* cookie) {
     PullAtomCallbackDeathCookie* cookie_ = static_cast<PullAtomCallbackDeathCookie*>(cookie);
-    sp<StatsPullerManager>& thiz = cookie_->mPullerManager;
+    sp<StatsPullerManager> thiz = cookie_->mPullerManager.promote();
+    if (!thiz) {
+        return;
+    }
+
     const PullerKey& pullerKey = cookie_->mPullerKey;
     wp<StatsPuller> puller = cookie_->mPuller;
 
diff --git a/cmds/statsd/src/external/puller_util.cpp b/cmds/statsd/src/external/puller_util.cpp
index aee7256..90247cf 100644
--- a/cmds/statsd/src/external/puller_util.cpp
+++ b/cmds/statsd/src/external/puller_util.cpp
@@ -49,10 +49,14 @@
  */
 void mapAndMergeIsolatedUidsToHostUid(vector<shared_ptr<LogEvent>>& data, const sp<UidMap>& uidMap,
                                       int tagId, const vector<int>& additiveFieldsVec) {
-    if ((android::util::AtomsInfo::kAtomsWithAttributionChain.find(tagId) ==
-         android::util::AtomsInfo::kAtomsWithAttributionChain.end()) &&
-        (android::util::AtomsInfo::kAtomsWithUidField.find(tagId) ==
-         android::util::AtomsInfo::kAtomsWithUidField.end())) {
+    bool hasAttributionChain = (android::util::AtomsInfo::kAtomsWithAttributionChain.find(tagId) !=
+                                android::util::AtomsInfo::kAtomsWithAttributionChain.end());
+    // To check if any LogEvent has a uid field, we can just check the first
+    // LogEvent because all atoms with this tagId should have the uid
+    // annotation.
+    bool hasUidField = (data[0]->getUidFieldIndex() != -1);
+
+    if (!hasAttributionChain && !hasUidField) {
         VLOG("No uid or attribution chain to merge, atom %d", tagId);
         return;
     }
@@ -75,19 +79,13 @@
                 }
             }
         } else {
-            auto it = android::util::AtomsInfo::kAtomsWithUidField.find(tagId);
-            if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) {
-                int uidField = it->second;  // uidField is the field number in proto,
-                // starting from 1
-                if (uidField > 0 && (int)event->getValues().size() >= uidField &&
-                    (event->getValues())[uidField - 1].mValue.getType() == INT) {
-                    Value& value = (*event->getMutableValues())[uidField - 1].mValue;
-                    const int hostUid = uidMap->getHostUidOrSelf(value.int_value);
-                    value.setInt(hostUid);
-                } else {
-                    ALOGE("Malformed log, uid not found. %s", event->ToString().c_str());
-                    return;
-                }
+            int uidFieldIndex = event->getUidFieldIndex();
+            if (uidFieldIndex != -1) {
+                Value& value = (*event->getMutableValues())[uidFieldIndex].mValue;
+                const int hostUid = uidMap->getHostUidOrSelf(value.int_value);
+                value.setInt(hostUid);
+            } else {
+                ALOGE("Malformed log, uid not found. %s", event->ToString().c_str());
             }
         }
     }
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 96bf04f..8b6a864 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -240,14 +240,20 @@
     last[1] = last[2] = false;
 }
 
+// Assumes that mValues is not empty
+bool LogEvent::checkPreviousValueType(Type expected) {
+    return mValues[mValues.size() - 1].mValue.getType() == expected;
+}
+
 void LogEvent::parseIsUidAnnotation(uint8_t annotationType) {
-    if (mValues.empty() || annotationType != BOOL_TYPE) {
+    if (mValues.empty() || !checkPreviousValueType(INT) || annotationType != BOOL_TYPE) {
         mValid = false;
         return;
     }
 
     bool isUid = readNextValue<uint8_t>();
     if (isUid) mUidFieldIndex = mValues.size() - 1;
+    mValues[mValues.size() - 1].mAnnotations.setUidField(isUid);
 }
 
 void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) {
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 9e21c77..4eeb7d6 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -213,6 +213,7 @@
     void parseExclusiveStateAnnotation(uint8_t annotationType);
     void parseTriggerStateResetAnnotation(uint8_t annotationType);
     void parseStateNestedAnnotation(uint8_t annotationType);
+    bool checkPreviousValueType(Type expected);
 
     /**
      * The below three variables are only valid during the execution of
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 1f8bbd7..2b4c6a3 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -81,18 +81,17 @@
     return matched;
 }
 
-bool tryMatchString(const UidMap& uidMap, const Field& field, const Value& value,
-                    const string& str_match) {
-    if (isAttributionUidField(field, value) || isUidField(field, value)) {
-        int uid = value.int_value;
+bool tryMatchString(const UidMap& uidMap, const FieldValue& fieldValue, const string& str_match) {
+    if (isAttributionUidField(fieldValue) || isUidField(fieldValue)) {
+        int uid = fieldValue.mValue.int_value;
         auto aidIt = UidMap::sAidToUidMapping.find(str_match);
         if (aidIt != UidMap::sAidToUidMapping.end()) {
             return ((int)aidIt->second) == uid;
         }
         std::set<string> packageNames = uidMap.getAppNamesFromUid(uid, true /* normalize*/);
         return packageNames.find(str_match) != packageNames.end();
-    } else if (value.getType() == STRING) {
-        return value.str_value == str_match;
+    } else if (fieldValue.mValue.getType() == STRING) {
+        return fieldValue.mValue.str_value == str_match;
     }
     return false;
 }
@@ -228,8 +227,7 @@
         }
         case FieldValueMatcher::ValueMatcherCase::kEqString: {
             for (int i = start; i < end; i++) {
-                if (tryMatchString(uidMap, values[i].mField, values[i].mValue,
-                                   matcher.eq_string())) {
+                if (tryMatchString(uidMap, values[i], matcher.eq_string())) {
                     return true;
                 }
             }
@@ -240,7 +238,7 @@
             for (int i = start; i < end; i++) {
                 bool notEqAll = true;
                 for (const auto& str : str_list.str_value()) {
-                    if (tryMatchString(uidMap, values[i].mField, values[i].mValue, str)) {
+                    if (tryMatchString(uidMap, values[i], str)) {
                         notEqAll = false;
                         break;
                     }
@@ -255,7 +253,7 @@
             const auto& str_list = matcher.eq_any_string();
             for (int i = start; i < end; i++) {
                 for (const auto& str : str_list.str_value()) {
-                    if (tryMatchString(uidMap, values[i].mField, values[i].mValue, str)) {
+                    if (tryMatchString(uidMap, values[i], str)) {
                         return true;
                     }
                 }
diff --git a/cmds/statsd/tests/FieldValue_test.cpp b/cmds/statsd/tests/FieldValue_test.cpp
index 0bf24f1..f5ba8fd 100644
--- a/cmds/statsd/tests/FieldValue_test.cpp
+++ b/cmds/statsd/tests/FieldValue_test.cpp
@@ -41,22 +41,10 @@
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeString(statsEvent, name.c_str());
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -66,22 +54,10 @@
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeInt32(statsEvent, value);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 }  // anonymous namespace
 
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 6f4c8e4..26423d4 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -18,6 +18,7 @@
 #include <log/logprint.h>
 #include <stdio.h>
 
+#include "annotations.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 #include "matchers/matcher_util.h"
 #include "stats_event.h"
@@ -48,15 +49,9 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
-
     AStatsEvent_writeInt32(statsEvent, value);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 void makeFloatLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -64,15 +59,9 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
-
     AStatsEvent_writeFloat(statsEvent, floatValue);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 void makeStringLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -80,32 +69,20 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
-
     AStatsEvent_writeString(statsEvent, name.c_str());
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
-void makeIntStringLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
-                           const int32_t value, const string& name) {
+void makeIntWithBoolAnnotationLogEvent(LogEvent* logEvent, const int32_t atomId,
+                                       const int32_t field, const uint8_t annotationId,
+                                       const bool annotationValue) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
-    AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
+    AStatsEvent_writeInt32(statsEvent, field);
+    AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue);
 
-    AStatsEvent_writeInt32(statsEvent, value);
-    AStatsEvent_writeString(statsEvent, name.c_str());
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 void makeAttributionLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -115,22 +92,10 @@
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeString(statsEvent, name.c_str());
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 void makeBoolLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
@@ -141,13 +106,8 @@
 
     AStatsEvent_writeBool(statsEvent, bool1);
     AStatsEvent_writeBool(statsEvent, bool2);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 }  // anonymous namespace
@@ -416,21 +376,20 @@
     simpleMatcher->add_field_value_matcher()->set_field(1);
     simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("pkg0");
 
-    // Set up the event
+    // Make event without is_uid annotation.
     LogEvent event1(/*uid=*/0, /*pid=*/0);
     makeIntLogEvent(&event1, TAG_ID, 0, 1111);
-
-    LogEvent event2(/*uid=*/0, /*pid=*/0);
-    makeIntStringLogEvent(&event2, TAG_ID_2, 0, 1111, "some value");
-
-    // Tag not in kAtomsWithUidField
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
 
-    // Tag found in kAtomsWithUidField and has matching uid
+    // Make event with is_uid annotation.
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeIntWithBoolAnnotationLogEvent(&event2, TAG_ID_2, 1111, ANNOTATION_ID_IS_UID, true);
+
+    // Event has is_uid annotation, so mapping from uid to package name occurs.
     simpleMatcher->set_atom_id(TAG_ID_2);
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
 
-    // Tag found in kAtomsWithUidField but has non-matching uid
+    // Event has is_uid annotation, but uid maps to different package name.
     simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("Pkg2");
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2));
 }
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 7febb35..ba5b032 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -67,22 +67,12 @@
     AStatsEvent_overwriteTimestamp(statsEvent, timestamp);
 
     vector<std::string> tags(uids.size()); // vector of empty strings
-    vector<const char*> cTags(uids.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = tags[i].c_str();
-    }
-    AStatsEvent_writeAttributionChain(statsEvent, reinterpret_cast<const uint32_t*>(uids.data()),
-                                      cTags.data(), uids.size());
+    writeAttribution(statsEvent, uids, tags);
 
     AStatsEvent_writeString(statsEvent, wl.c_str());
     AStatsEvent_writeInt32(statsEvent, acquire);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 } // anonymous namespace
diff --git a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
index 81e1c05..60403f2 100644
--- a/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
+++ b/cmds/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
@@ -84,14 +84,9 @@
     AStatsEvent_writeString(statsEvent, calling_pkg_name.c_str());
     AStatsEvent_writeInt32(statsEvent, is_instant_app);
     AStatsEvent_writeInt32(statsEvent, activity_start_msec);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
diff --git a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
index 6aff9ef..4b9bac1 100644
--- a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
+++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
@@ -190,13 +190,13 @@
     int32_t uid = 123;
     values.push_back(value);
 
-    StatsPullerManager pullerManager;
-    pullerManager.RegisterPullAtomCallback(uid, pullTagId, pullCoolDownNs, pullTimeoutNs,
-                                           vector<int32_t>(), cb);
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    pullerManager->RegisterPullAtomCallback(uid, pullTagId, pullCoolDownNs, pullTimeoutNs,
+                                            vector<int32_t>(), cb);
     vector<shared_ptr<LogEvent>> dataHolder;
     int64_t startTimeNs = getElapsedRealtimeNs();
     // Returns false, since StatsPuller code will evaluate the timeout.
-    EXPECT_FALSE(pullerManager.Pull(pullTagId, {uid}, &dataHolder));
+    EXPECT_FALSE(pullerManager->Pull(pullTagId, {uid}, &dataHolder));
     int64_t endTimeNs = getElapsedRealtimeNs();
     int64_t actualPullDurationNs = endTimeNs - startTimeNs;
 
diff --git a/cmds/statsd/tests/external/StatsPuller_test.cpp b/cmds/statsd/tests/external/StatsPuller_test.cpp
index e8200d5..5043358 100644
--- a/cmds/statsd/tests/external/StatsPuller_test.cpp
+++ b/cmds/statsd/tests/external/StatsPuller_test.cpp
@@ -64,16 +64,10 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, pullTagId);
     AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
-
     AStatsEvent_writeInt64(statsEvent, value);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
diff --git a/cmds/statsd/tests/external/puller_util_test.cpp b/cmds/statsd/tests/external/puller_util_test.cpp
index 15425d8..c2cfb37 100644
--- a/cmds/statsd/tests/external/puller_util_test.cpp
+++ b/cmds/statsd/tests/external/puller_util_test.cpp
@@ -21,8 +21,10 @@
 #include <vector>
 
 #include "../metrics/metrics_test_helper.h"
+#include "annotations.h"
 #include "stats_event.h"
 #include "statslog_statsdtest.h"
+#include "tests/statsd_test_util.h"
 
 #ifdef __ANDROID__
 
@@ -52,15 +54,15 @@
 
 void extractIntoVector(vector<shared_ptr<LogEvent>> events,
                       vector<vector<int>>& ret) {
-  ret.clear();
-  status_t err;
-  for (const auto& event : events) {
-    vector<int> vec;
-    vec.push_back(event->GetInt(1, &err));
-    vec.push_back(event->GetInt(2, &err));
-    vec.push_back(event->GetInt(3, &err));
-    ret.push_back(vec);
-  }
+    ret.clear();
+    status_t err;
+    for (const auto& event : events) {
+        vector<int> vec;
+        vec.push_back(event->GetInt(1, &err));
+        vec.push_back(event->GetInt(2, &err));
+        vec.push_back(event->GetInt(3, &err));
+        ret.push_back(vec);
+    }
 }
 
 std::shared_ptr<LogEvent> makeUidLogEvent(uint64_t timestampNs, int uid, int data1, int data2) {
@@ -69,16 +71,12 @@
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
     AStatsEvent_writeInt32(statsEvent, uid);
+    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
     AStatsEvent_writeInt32(statsEvent, data1);
     AStatsEvent_writeInt32(statsEvent, data2);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::shared_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -86,16 +84,10 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, nonUidAtomTagId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeInt32(statsEvent, data1);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::shared_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
diff --git a/cmds/statsd/tests/log_event/LogEventQueue_test.cpp b/cmds/statsd/tests/log_event/LogEventQueue_test.cpp
index 6dc041f..a15f95b 100644
--- a/cmds/statsd/tests/log_event/LogEventQueue_test.cpp
+++ b/cmds/statsd/tests/log_event/LogEventQueue_test.cpp
@@ -21,6 +21,7 @@
 #include <thread>
 
 #include "stats_event.h"
+#include "tests/statsd_test_util.h"
 
 namespace android {
 namespace os {
@@ -37,14 +38,9 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, 10);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index d55996c..65f8de6 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -46,26 +46,17 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId, string uid) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeString(statsEvent, uid.c_str());
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 }  // namespace
diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
index 6143dc0..30f8159 100644
--- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -26,6 +26,7 @@
 #include "src/condition/ConditionWizard.h"
 #include "src/stats_log_util.h"
 #include "stats_event.h"
+#include "tests/statsd_test_util.h"
 
 using namespace android::os::statsd;
 using namespace testing;
@@ -48,12 +49,8 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
 }  // namespace
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
index e58bbb7..97647a7 100644
--- a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -43,14 +43,9 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeString(statsEvent, str.c_str());
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 }  // anonymous namespace
 
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 2fe05a4..42d0d5d 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -64,14 +64,9 @@
     AStatsEvent_writeInt32(statsEvent, value1);
     AStatsEvent_writeString(statsEvent, str1.c_str());
     AStatsEvent_writeInt32(statsEvent, value2);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
     shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
-
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 }  // anonymous namespace
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index b623a09..009e49a5 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -611,7 +611,7 @@
 
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 110));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8});
 
@@ -656,7 +656,7 @@
     valueProducer.prepareFirstBucket();
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
 
@@ -665,14 +665,14 @@
     EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 1, 10);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 10);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
     EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
 
     // Next value should create a new bucket.
     LogEvent event3(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 1, 10);
+    CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 10);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
     EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs);
@@ -812,10 +812,10 @@
     valueProducer.prepareFirstBucket();
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
 
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has one slice
@@ -856,7 +856,7 @@
     valueProducer.mCondition = ConditionState::kFalse;
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has 1 slice
     EXPECT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size());
@@ -864,7 +864,7 @@
     valueProducer.onConditionChangedLocked(true, bucketStartTimeNs + 15);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
 
     // has one slice
@@ -875,7 +875,7 @@
     EXPECT_EQ(20, curInterval.value.long_value);
 
     LogEvent event3(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event3, tagId, bucketStartTimeNs + 30, 1, 30);
+    CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 30, 30);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
 
     // has one slice
@@ -886,7 +886,7 @@
     valueProducer.onConditionChangedLocked(false, bucketStartTimeNs + 35);
 
     LogEvent event4(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event4, tagId, bucketStartTimeNs + 40, 1, 40);
+    CreateRepeatedValueLogEvent(&event4, tagId, bucketStartTimeNs + 40, 40);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
 
     // has one slice
@@ -1195,10 +1195,10 @@
     valueProducer.prepareFirstBucket();
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
 
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has one slice
@@ -1238,10 +1238,10 @@
     valueProducer.prepareFirstBucket();
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 20);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
 
     // has one slice
@@ -1283,10 +1283,10 @@
     valueProducer.prepareFirstBucket();
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 15);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has one slice
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
@@ -1331,10 +1331,10 @@
     valueProducer.prepareFirstBucket();
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 1, 15);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has one slice
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
@@ -1374,10 +1374,10 @@
     valueProducer.prepareFirstBucket();
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 1, 10);
+    CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
 
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 1, 15);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     // has one slice
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
@@ -1398,7 +1398,7 @@
 
     // no change in data.
     LogEvent event3(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 1, 15);
+    CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
@@ -1408,7 +1408,7 @@
     EXPECT_EQ(true, curInterval.hasValue);
 
     LogEvent event4(/*uid=*/0, /*pid=*/0);
-    CreateTwoValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 1, 15);
+    CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event4);
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
@@ -2166,7 +2166,7 @@
     // Bucket start.
     vector<shared_ptr<LogEvent>> allData;
     allData.clear();
-    allData.push_back(CreateTwoValueLogEvent(tagId, bucketStartTimeNs + 1, 1, 110));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucketStartTimeNs + 1, 110));
     valueProducer->onDataPulled(allData, /** succeed */ false, bucketStartTimeNs);
 
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 2);
@@ -2174,7 +2174,7 @@
 
     // Bucket end.
     allData.clear();
-    allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 140));
+    allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 140));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     valueProducer->flushIfNeededLocked(bucket2StartTimeNs + 1);
diff --git a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
index ac3ad69..7b952d7 100644
--- a/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
+++ b/cmds/statsd/tests/shell/ShellSubscriber_test.cpp
@@ -171,13 +171,9 @@
     AStatsEvent_overwriteTimestamp(statsEvent, 1111L);
     AStatsEvent_writeInt32(statsEvent, uid);
     AStatsEvent_writeInt64(statsEvent, timeMillis);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index a5b8e1c..78c80bc 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -62,7 +62,7 @@
 
 // START: build event functions.
 // Incorrect event - missing fields
-std::shared_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName,
+std::unique_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName,
                                                      int state) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::OVERLAY_STATE_CHANGED);
@@ -72,14 +72,9 @@
     AStatsEvent_writeString(statsEvent, packageName.c_str());
     // Missing field 3 - using_alert_window.
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -93,14 +88,9 @@
     AStatsEvent_writeString(statsEvent, packageName.c_str());
     AStatsEvent_writeInt32(statsEvent, true);       // using_alert_window
     AStatsEvent_writeString(statsEvent, "string");  // exclusive state: string instead of int
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 // END: build event functions.
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 7d765d3..ed3cf5b 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -507,23 +507,26 @@
 }
 // END: get primary key functions
 
-shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
-                                            int32_t value2) {
-    AStatsEvent* statsEvent = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(statsEvent, atomId);
-    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+                      const vector<string>& attributionTags) {
+    vector<const char*> cTags(attributionTags.size());
+    for (int i = 0; i < cTags.size(); i++) {
+        cTags[i] = attributionTags[i].c_str();
+    }
 
-    AStatsEvent_writeInt32(statsEvent, value1);
-    AStatsEvent_writeInt32(statsEvent, value2);
+    AStatsEvent_writeAttributionChain(statsEvent,
+                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
+                                      cTags.data(), attributionUids.size());
+}
+
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent) {
     AStatsEvent_build(statsEvent);
 
     size_t size;
     uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
     logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
 
-    return logEvent;
+    AStatsEvent_release(statsEvent);
 }
 
 void CreateTwoValueLogEvent(LogEvent* logEvent, int atomId, int64_t eventTimeNs, int32_t value1,
@@ -534,31 +537,14 @@
 
     AStatsEvent_writeInt32(statsEvent, value1);
     AStatsEvent_writeInt32(statsEvent, value2);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
-shared_ptr<LogEvent> CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
-                                              int32_t value2, int32_t value3) {
-    AStatsEvent* statsEvent = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(statsEvent, atomId);
-    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
-
-    AStatsEvent_writeInt32(statsEvent, value1);
-    AStatsEvent_writeInt32(statsEvent, value2);
-    AStatsEvent_writeInt32(statsEvent, value3);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
+                                            int32_t value2) {
     shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
-
+    CreateTwoValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2);
     return logEvent;
 }
 
@@ -571,29 +557,14 @@
     AStatsEvent_writeInt32(statsEvent, value1);
     AStatsEvent_writeInt32(statsEvent, value2);
     AStatsEvent_writeInt32(statsEvent, value3);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
-shared_ptr<LogEvent> CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value) {
-    AStatsEvent* statsEvent = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(statsEvent, atomId);
-    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
-
-    AStatsEvent_writeInt32(statsEvent, value);
-    AStatsEvent_writeInt32(statsEvent, value);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+shared_ptr<LogEvent> CreateThreeValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
+                                              int32_t value2, int32_t value3) {
     shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
-
+    CreateThreeValueLogEvent(logEvent.get(), atomId, eventTimeNs, value1, value2, value3);
     return logEvent;
 }
 
@@ -605,26 +576,13 @@
 
     AStatsEvent_writeInt32(statsEvent, value);
     AStatsEvent_writeInt32(statsEvent, value);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
-shared_ptr<LogEvent> CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs) {
-    AStatsEvent* statsEvent = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(statsEvent, atomId);
-    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+shared_ptr<LogEvent> CreateRepeatedValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value) {
     shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
-
+    CreateRepeatedValueLogEvent(logEvent.get(), atomId, eventTimeNs, value);
     return logEvent;
 }
 
@@ -632,12 +590,14 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
     AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
-    AStatsEvent_build(statsEvent);
 
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+shared_ptr<LogEvent> CreateNoValuesLogEvent(int atomId, int64_t eventTimeNs) {
+    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+    CreateNoValuesLogEvent(logEvent.get(), atomId, eventTimeNs);
+    return logEvent;
 }
 
 std::unique_ptr<LogEvent> CreateScreenStateChangedEvent(
@@ -645,16 +605,10 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::SCREEN_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -662,16 +616,10 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::ON);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -679,16 +627,10 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::BATTERY_SAVER_MODE_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeInt32(statsEvent, BatterySaverModeStateChanged::OFF);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -696,16 +638,10 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -713,16 +649,10 @@
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::SCREEN_BRIGHTNESS_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
-
     AStatsEvent_writeInt32(statsEvent, level);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -733,24 +663,12 @@
     AStatsEvent_setAtomId(statsEvent, util::SCHEDULED_JOB_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeString(statsEvent, jobName.c_str());
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -780,25 +698,13 @@
     AStatsEvent_setAtomId(statsEvent, util::WAKELOCK_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeInt32(statsEvent, android::os::WakeLockLevelEnum::PARTIAL_WAKE_LOCK);
     AStatsEvent_writeString(statsEvent, wakelockName.c_str());
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -828,14 +734,9 @@
     AStatsEvent_writeString(statsEvent, "pkg_name");
     AStatsEvent_writeString(statsEvent, "class_name");
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -858,24 +759,12 @@
     AStatsEvent_setAtomId(statsEvent, util::SYNC_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeString(statsEvent, name.c_str());
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -904,14 +793,9 @@
     AStatsEvent_writeInt32(statsEvent, uid);
     AStatsEvent_writeString(statsEvent, "");
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -928,14 +812,9 @@
     AStatsEvent_writeInt32(statsEvent, uid);
     AStatsEvent_writeString(statsEvent, "eventType");
     AStatsEvent_writeString(statsEvent, "processName");
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -948,14 +827,9 @@
     AStatsEvent_writeInt32(statsEvent, hostUid);
     AStatsEvent_writeInt32(statsEvent, isolatedUid);
     AStatsEvent_writeInt32(statsEvent, is_create);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -967,14 +841,9 @@
 
     AStatsEvent_writeInt32(statsEvent, uid);
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -988,26 +857,14 @@
     AStatsEvent_setAtomId(statsEvent, util::BLE_SCAN_STATE_CHANGED);
     AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
 
-    vector<const char*> cTags(attributionTags.size());
-    for (int i = 0; i < cTags.size(); i++) {
-        cTags[i] = attributionTags[i].c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(statsEvent,
-                                      reinterpret_cast<const uint32_t*>(attributionUids.data()),
-                                      cTags.data(), attributionUids.size());
+    writeAttribution(statsEvent, attributionUids, attributionTags);
     AStatsEvent_writeInt32(statsEvent, state);
     AStatsEvent_writeInt32(statsEvent, filtered);       // filtered
     AStatsEvent_writeInt32(statsEvent, firstMatch);     // first match
     AStatsEvent_writeInt32(statsEvent, opportunistic);  // opportunistic
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
@@ -1023,14 +880,9 @@
     AStatsEvent_writeString(statsEvent, packageName.c_str());
     AStatsEvent_writeInt32(statsEvent, usingAlertWindow);
     AStatsEvent_writeInt32(statsEvent, state);
-    AStatsEvent_build(statsEvent);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
 
     std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
-    logEvent->parseBuffer(buf, size);
-    AStatsEvent_release(statsEvent);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
     return logEvent;
 }
 
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index f24705a..d6ea77eb 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -25,6 +25,7 @@
 #include "src/hash.h"
 #include "src/logd/LogEvent.h"
 #include "src/stats_log_util.h"
+#include "stats_event.h"
 #include "statslog_statsdtest.h"
 
 namespace android {
@@ -189,6 +190,12 @@
 void getPartialWakelockKey(int uid, HashableDimensionKey* key);
 // END: get primary key functions
 
+void writeAttribution(AStatsEvent* statsEvent, const vector<int>& attributionUids,
+                      const vector<string>& attributionTags);
+
+// Builds statsEvent to get buffer that is parsed into logEvent then releases statsEvent.
+void parseStatsEventToLogEvent(AStatsEvent* statsEvent, LogEvent* logEvent);
+
 shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
                                             int32_t value2);
 
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index 2a7cfd3..54a744b 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -64,10 +64,12 @@
             "AID_LMKD",
             "com.android.managedprovisioning",
             "AID_MEDIA",
-            "AID_NETWORK_STACK"
+            "AID_NETWORK_STACK",
+            "com.google.android.providers.media.module",
     };
     private static final String[] DEFAULT_PULL_SOURCES = {
             "AID_SYSTEM",
+            "AID_RADIO"
     };
     private static final Logger LOGGER = Logger.getLogger(TestDrive.class.getName());
 
diff --git a/cmds/svc/src/com/android/commands/svc/PowerCommand.java b/cmds/svc/src/com/android/commands/svc/PowerCommand.java
index 3180b77..957ebfb 100644
--- a/cmds/svc/src/com/android/commands/svc/PowerCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/PowerCommand.java
@@ -24,6 +24,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.sysprop.InitProperties;
 
 public class PowerCommand extends Svc.Command {
     private static final int FORCE_SUSPEND_DELAY_DEFAULT_MILLIS = 0;
@@ -103,6 +104,8 @@
                         pm.reboot(false, mode, true);
                     } catch (RemoteException e) {
                         maybeLogRemoteException("Failed to reboot.");
+                    } catch (Exception e) {
+                        System.err.println("Failed to reboot: " + e.getMessage());
                     }
                     return;
                 } else if ("shutdown".equals(args[1])) {
@@ -138,7 +141,9 @@
     // if it is already in shutdown flow.
     private void maybeLogRemoteException(String msg) {
         String powerProp = SystemProperties.get("sys.powerctl");
-        if (powerProp.isEmpty()) {
+        // Also check if userspace reboot is ongoing, since in case of userspace reboot value of the
+        // sys.powerctl property will be reset.
+        if (powerProp.isEmpty() && !InitProperties.userspace_reboot_in_progress().orElse(false)) {
             System.err.println(msg);
         }
     }
diff --git a/core/java/android/app/AppOps.md b/core/java/android/app/AppOps.md
index bee701a..ad1a301 100644
--- a/core/java/android/app/AppOps.md
+++ b/core/java/android/app/AppOps.md
@@ -116,14 +116,27 @@
 can fluctuate when switching apps. By delaying the change the appops service is not affected by
 those.
 
-The proc state is used for two use cases: Firstly, Tracking remembers the proc state for each
-tracked event. Secondly, `noteOp`/`checkOp` calls for app-op that are set to `MODE_FOREGROUND` are
-translated using the `AppOpsService.UidState.evalMode` method into `MODE_ALLOWED` when the app is
-counted as foreground and `MODE_IGNORED` when the app is counted as background. `checkOpRaw`
-calls are not affected.
+In addition to proc state, the `AppOpsService` also receives process capability update from the
+`ActivityManagerService`. Proc capability specifies what while-in-use(`MODE_FOREGROUND`) operations
+ the proc is allowed to perform in its current proc state. There are three proc capabilities
+ defined so far: 
+`PROCESS_CAPABILITY_FOREGROUND_LOCATION`, `PROCESS_CAPABILITY_FOREGROUND_CAMERA` and
+`PROCESS_CAPABILITY_FOREGROUND_MICROPHONE`, they correspond to the while-in-use operation of
+location, camera and microphone (microphone is `RECORD_AUDIO`).
 
-The current proc state for an app can be read from `dumpsys appops`. The tracking information can
-be read from `dumpsys appops`
+In `ActivityManagerService`, `PROCESS_STATE_TOP` and `PROCESS_STATE_PERSISTENT` have all
+three capabilities, `PROCESS_STATE_FOREGROUND_SERVICE` has capabilities defined by
+ `foregroundServiceType` that is specified in foreground service's manifest file. A client process 
+ can pass its capabilities to service using `BIND_INCLUDE_CAPABILITIES` flag.
+
+The proc state and capability are used for two use cases: Firstly, Tracking remembers the proc state
+ for each tracked event. Secondly, `noteOp`/`checkOp` calls for app-op that are set to
+ `MODE_FOREGROUND` are translated using the `AppOpsService.UidState.evalMode` method into
+ `MODE_ALLOWED` when the app has the capability and `MODE_IGNORED` when the app does not have the 
+ capability. `checkOpRaw` calls are not affected.
+
+The current proc state and capability for an app can be read from `dumpsys appops`.
+The tracking information can be read from `dumpsys appops`
 
 ```
 Uid u0a118:
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 46b06fb..3a708a6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1395,6 +1395,7 @@
     public static final String OPSTR_QUERY_ALL_PACKAGES = "android:query_all_packages";
     /** @hide Access all external storage */
     @SystemApi
+    @TestApi
     public static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
             "android:manage_external_storage";
 
diff --git a/core/java/android/app/IWindowToken.aidl b/core/java/android/app/IWindowToken.aidl
index 8ea881f..3627b0f 100644
--- a/core/java/android/app/IWindowToken.aidl
+++ b/core/java/android/app/IWindowToken.aidl
@@ -30,4 +30,6 @@
  */
 oneway interface IWindowToken {
     void onConfigurationChanged(in Configuration newConfig, int newDisplayId);
+
+    void onWindowTokenRemoved();
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7a59349..babeffc 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7589,10 +7589,8 @@
                     >= Build.VERSION_CODES.P;
             boolean isOneToOne;
             CharSequence nameReplacement = null;
-            Icon avatarReplacement = null;
             if (!atLeastP) {
                 isOneToOne = TextUtils.isEmpty(conversationTitle);
-                avatarReplacement = mBuilder.mN.mLargeIcon;
                 if (hasOnlyWhiteSpaceSenders()) {
                     isOneToOne = true;
                     nameReplacement = conversationTitle;
@@ -7641,7 +7639,7 @@
             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
                     isCollapsed);
             contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement",
-                    avatarReplacement);
+                    mBuilder.mN.mLargeIcon);
             contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
                     nameReplacement);
             contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index d00366b..47ccc2f 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -344,7 +344,7 @@
         ApkAssets apkAssets = null;
         if (mLoadedApkAssets != null) {
             apkAssets = mLoadedApkAssets.get(newKey);
-            if (apkAssets != null) {
+            if (apkAssets != null && apkAssets.isUpToDate()) {
                 return apkAssets;
             }
         }
@@ -353,7 +353,7 @@
         final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
         if (apkAssetsRef != null) {
             apkAssets = apkAssetsRef.get();
-            if (apkAssets != null) {
+            if (apkAssets != null && apkAssets.isUpToDate()) {
                 if (mLoadedApkAssets != null) {
                     mLoadedApkAssets.put(newKey, apkAssets);
                 }
@@ -1121,7 +1121,9 @@
             daj = new DisplayAdjustments(daj);
             daj.setCompatibilityInfo(compat);
         }
-        daj.setConfiguration(config);
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            daj.setConfiguration(config);
+        }
         DisplayMetrics dm = getDisplayMetrics(displayId, daj);
         if (displayId != Display.DEFAULT_DISPLAY) {
             applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 91a8572..e599a5c 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1658,6 +1658,9 @@
         public final T getService(ContextImpl ctx) {
             final Object[] cache = ctx.mServiceCache;
             final int[] gates = ctx.mServiceInitializationStateArray;
+            boolean interrupted = false;
+
+            T ret = null;
 
             for (;;) {
                 boolean doInitialize = false;
@@ -1665,7 +1668,8 @@
                     // Return it if we already have a cached instance.
                     T service = (T) cache[mCacheIndex];
                     if (service != null || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
-                        return service;
+                        ret = service;
+                        break; // exit the for (;;)
                     }
 
                     // If we get here, there's no cached instance.
@@ -1708,24 +1712,33 @@
                             cache.notifyAll();
                         }
                     }
-                    return service;
+                    ret = service;
+                    break; // exit the for (;;)
                 }
                 // The other threads will wait for the first thread to call notifyAll(),
                 // and go back to the top and retry.
                 synchronized (cache) {
+                    // Repeat until the state becomes STATE_READY or STATE_NOT_FOUND.
+                    // We can't respond to interrupts here; just like we can't in the "doInitialize"
+                    // path, so we remember the interrupt state here and re-interrupt later.
                     while (gates[mCacheIndex] < ContextImpl.STATE_READY) {
                         try {
+                            // Clear the interrupt state.
+                            interrupted |= Thread.interrupted();
                             cache.wait();
                         } catch (InterruptedException e) {
                             // This shouldn't normally happen, but if someone interrupts the
                             // thread, it will.
-                            Slog.wtf(TAG, "getService() interrupted");
-                            Thread.currentThread().interrupt();
-                            return null;
+                            Slog.w(TAG, "getService() interrupted");
+                            interrupted = true;
                         }
                     }
                 }
             }
+            if (interrupted) {
+                Thread.currentThread().interrupt();
+            }
+            return ret;
         }
 
         public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
diff --git a/core/java/android/app/WindowContext.java b/core/java/android/app/WindowContext.java
index 878993e..3a06c9d 100644
--- a/core/java/android/app/WindowContext.java
+++ b/core/java/android/app/WindowContext.java
@@ -28,6 +28,8 @@
 import android.view.WindowManagerGlobal;
 import android.view.WindowManagerImpl;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.ref.Reference;
 
 /**
@@ -75,8 +77,6 @@
             // config back to the client.
             result = mWms.addWindowTokenWithOptions(
                     mToken, type, getDisplayId(), options, getPackageName());
-
-            // TODO(window-context): remove token with a DeathObserver
         }  catch (RemoteException e) {
             mOwnsToken = false;
             throw e.rethrowFromSystemServer();
@@ -100,6 +100,13 @@
 
     @Override
     protected void finalize() throws Throwable {
+        release();
+        super.finalize();
+    }
+
+    /** Used for test to invoke because we can't invoke finalize directly. */
+    @VisibleForTesting
+    public void release() {
         if (mOwnsToken) {
             try {
                 mWms.removeWindowToken(mToken, getDisplayId());
@@ -108,6 +115,12 @@
                 throw e.rethrowFromSystemServer();
             }
         }
-        super.finalize();
+        destroy();
+    }
+
+    void destroy() {
+        final ContextImpl impl = (ContextImpl) getBaseContext();
+        impl.scheduleFinalCleanup(getClass().getName(), "WindowContext");
+        Reference.reachabilityFence(this);
     }
 }
diff --git a/core/java/android/app/WindowTokenClient.java b/core/java/android/app/WindowTokenClient.java
index ed0179b..301960e 100644
--- a/core/java/android/app/WindowTokenClient.java
+++ b/core/java/android/app/WindowTokenClient.java
@@ -20,6 +20,9 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.view.WindowManagerGlobal;
+
+import java.lang.ref.WeakReference;
 
 /**
  * Client implementation of {@link IWindowToken}. It can receive configuration change callbacks from
@@ -31,9 +34,9 @@
 public class WindowTokenClient extends IWindowToken.Stub {
     /**
      * Attached {@link Context} for this window token to update configuration and resources.
-     * Initialized by {@link #attachContext(Context)}.
+     * Initialized by {@link #attachContext(WindowContext)}.
      */
-    private Context mContext = null;
+    private WeakReference<WindowContext> mContextRef = null;
 
     private final ResourcesManager mResourcesManager = ResourcesManager.getInstance();
 
@@ -47,30 +50,46 @@
      * @param context context to be attached
      * @throws IllegalStateException if attached context has already existed.
      */
-    void attachContext(@NonNull Context context) {
-        if (mContext != null) {
+    void attachContext(@NonNull WindowContext context) {
+        if (mContextRef != null) {
             throw new IllegalStateException("Context is already attached.");
         }
-        mContext = context;
-        ContextImpl impl = ContextImpl.getImpl(mContext);
+        mContextRef = new WeakReference<>(context);
+        final ContextImpl impl = ContextImpl.getImpl(context);
         impl.setResources(impl.createWindowContextResources());
     }
 
     @Override
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
-        final int currentDisplayId = mContext.getDisplayId();
+        final Context context = mContextRef.get();
+        if (context == null) {
+            return;
+        }
+        final int currentDisplayId = context.getDisplayId();
         final boolean displayChanged = newDisplayId != currentDisplayId;
-        final Configuration config = new Configuration(mContext.getResources()
+        final Configuration config = new Configuration(context.getResources()
                 .getConfiguration());
         final boolean configChanged = config.isOtherSeqNewer(newConfig)
                 && config.updateFrom(newConfig) != 0;
         if (displayChanged || configChanged) {
             // TODO(ag/9789103): update resource manager logic to track non-activity tokens
-            mResourcesManager.updateResourcesForActivity(asBinder(), config, newDisplayId,
+            mResourcesManager.updateResourcesForActivity(this, config, newDisplayId,
                     displayChanged);
         }
         if (displayChanged) {
-            mContext.updateDisplay(newDisplayId);
+            context.updateDisplay(newDisplayId);
         }
     }
+
+    @Override
+    public void onWindowTokenRemoved() {
+        final WindowContext context = mContextRef.get();
+        if (context != null) {
+            context.destroy();
+            mContextRef.clear();
+        }
+        // If a secondary display is detached, release all views attached to this token.
+        WindowManagerGlobal.getInstance().closeAll(this, mContextRef.getClass().getName(),
+                "WindowContext");
+    }
 }
diff --git a/core/java/android/app/admin/DevicePolicyKeyguardService.java b/core/java/android/app/admin/DevicePolicyKeyguardService.java
index db833ec..473725f 100644
--- a/core/java/android/app/admin/DevicePolicyKeyguardService.java
+++ b/core/java/android/app/admin/DevicePolicyKeyguardService.java
@@ -16,12 +16,15 @@
 
 package android.app.admin;
 
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.content.Intent;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.SurfaceControlViewHost;
@@ -41,27 +44,34 @@
 @SystemApi
 public class DevicePolicyKeyguardService extends Service {
     private static final String TAG = "DevicePolicyKeyguardService";
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
     private IKeyguardCallback mCallback;
 
     private final IKeyguardClient mClient = new IKeyguardClient.Stub() {
+        @MainThread
         @Override
         public void onCreateKeyguardSurface(@Nullable IBinder hostInputToken,
-                IKeyguardCallback callback) {
+                @NonNull IKeyguardCallback callback) {
             mCallback = callback;
-            SurfaceControlViewHost.SurfacePackage surfacePackage =
-                    DevicePolicyKeyguardService.this.onCreateKeyguardSurface(hostInputToken);
+            mHandler.post(() -> {
+                SurfaceControlViewHost.SurfacePackage surfacePackage =
+                        DevicePolicyKeyguardService.this.onCreateKeyguardSurface(hostInputToken);
 
-            if (mCallback != null) {
                 try {
                     mCallback.onRemoteContentReady(surfacePackage);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to return created SurfacePackage", e);
                 }
-            }
+            });
         }
     };
 
     @Override
+    public void onDestroy() {
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
+    @Override
     @Nullable
     public final IBinder onBind(@Nullable Intent intent) {
         return mClient.asBinder();
@@ -97,6 +107,10 @@
      */
     @Nullable
     public void dismiss() {
+        if (mCallback == null) {
+            Log.w(TAG, "KeyguardCallback was unexpectedly null");
+            return;
+        }
         try {
             mCallback.onDismiss();
         } catch (RemoteException e) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index faf9ec6..fb9adb7 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -8606,7 +8606,7 @@
      * <p>
      * This method may be called on the {@code DevicePolicyManager} instance returned from
      * {@link #getParentProfileInstance(ComponentName)}. Note that only a profile owner on
-     * an organization-deviced can affect account types on the parent profile instance.
+     * an organization-owned device can affect account types on the parent profile instance.
      *
      * @return a list of account types for which account management has been disabled.
      *
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index fc48e7f..f216db6 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -979,14 +979,17 @@
                 8, BLUETOOTH_GET_STATE_CACHE_PROPERTY) {
                 @Override
                 protected Integer recompute(Void query) {
-                    // This function must be called while holding the
-                    // mServiceLock, and with mService not null. The public
-                    // getState() method makes this guarantee.
                     try {
-                        return mService.getState();
+                        mServiceLock.readLock().lock();
+                        if (mService != null) {
+                            return mService.getState();
+                        }
                     } catch (RemoteException e) {
-                        throw e.rethrowFromSystemServer();
+                        Log.e(TAG, "", e);
+                    } finally {
+                        mServiceLock.readLock().unlock();
                     }
+                    return BluetoothAdapter.STATE_OFF;
                 }
             };
 
@@ -1013,24 +1016,7 @@
     @RequiresPermission(Manifest.permission.BLUETOOTH)
     @AdapterState
     public int getState() {
-        int state = BluetoothAdapter.STATE_OFF;
-
-        try {
-            mServiceLock.readLock().lock();
-            // The test for mService must either be outside the cache, or
-            // the cache must be invalidated when mService changes.
-            if (mService != null) {
-                state = mBluetoothGetStateCache.query(null);
-            }
-        } catch (RuntimeException e) {
-            if (e.getCause() instanceof RemoteException) {
-                Log.e(TAG, "", e.getCause());
-            } else {
-                throw e;
-            }
-        } finally {
-            mServiceLock.readLock().unlock();
-        }
+        int state = mBluetoothGetStateCache.query(null);
 
         // Consider all internal states as OFF
         if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_ON
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index e446f4f..0a4627d 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -687,6 +687,19 @@
     public static final int NOTIFY_DELETE = 1 << 4;
 
     /**
+     * Flag for {@link #notifyChange(Uri, ContentObserver, int)}: typically set
+     * by a {@link ContentProvider} to indicate that this notification should
+     * not be subject to any delays when dispatching to apps running in the
+     * background.
+     * <p>
+     * Using this flag may negatively impact system health and performance, and
+     * should be used sparingly.
+     *
+     * @hide
+     */
+    public static final int NOTIFY_NO_DELAY = 1 << 15;
+
+    /**
      * No exception, throttled by app standby normally.
      * @hide
      */
diff --git a/core/java/android/content/pm/FileSystemControlParcel.aidl b/core/java/android/content/pm/FileSystemControlParcel.aidl
index f00feae..92df16c 100644
--- a/core/java/android/content/pm/FileSystemControlParcel.aidl
+++ b/core/java/android/content/pm/FileSystemControlParcel.aidl
@@ -17,6 +17,7 @@
 package android.content.pm;
 
 import android.content.pm.IPackageInstallerSessionFileSystemConnector;
+import android.os.incremental.IIncrementalServiceConnector;
 import android.os.incremental.IncrementalFileSystemControlParcel;
 
 /**
@@ -26,6 +27,8 @@
 parcelable FileSystemControlParcel {
     // Incremental FS control descriptors.
     @nullable IncrementalFileSystemControlParcel incremental;
+    // Incremental FS service.
+    @nullable IIncrementalServiceConnector service;
     // Callback-based installation connector.
     @nullable IPackageInstallerSessionFileSystemConnector callback;
 }
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index c94d428..6bd8b1d 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -21,7 +21,6 @@
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION;
 import static android.os.Build.VERSION_CODES.DONUT;
@@ -253,10 +252,8 @@
             final File baseApk = new File(lite.baseCodePath);
             ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,
                     lite.codePath, assets, flags);
-            // TODO(b/135203078): Pass original error up?
             if (result.isError()) {
-                return input.error(INSTALL_PARSE_FAILED_NOT_APK,
-                        "Failed to parse base APK: " + baseApk);
+                return input.error(result);
             }
 
             ParsingPackage pkg = result.getResult();
diff --git a/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java b/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java
index 6f01796..5d4b48d 100644
--- a/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java
+++ b/core/java/android/database/sqlite/SQLiteCantOpenDatabaseException.java
@@ -22,4 +22,9 @@
     public SQLiteCantOpenDatabaseException(String error) {
         super(error);
     }
+
+    /** @hide */
+    public SQLiteCantOpenDatabaseException(String error, Throwable cause) {
+        super(error, cause);
+    }
 }
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index bcb3934..f7c96a3 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -36,6 +36,9 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
@@ -215,12 +218,31 @@
     }
 
     private void open() {
+        final String file = mConfiguration.path;
         final int cookie = mRecentOperations.beginOperation("open", null, null);
         try {
-            mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags,
+            mConnectionPtr = nativeOpen(file, mConfiguration.openFlags,
                     mConfiguration.label,
                     NoPreloadHolder.DEBUG_SQL_STATEMENTS, NoPreloadHolder.DEBUG_SQL_TIME,
                     mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount);
+        } catch (SQLiteCantOpenDatabaseException e) {
+            String message = String.format("Cannot open database '%s'", file);
+
+            final Path path = FileSystems.getDefault().getPath(file);
+            final Path dir = path.getParent();
+
+            if (!Files.isDirectory(dir)) {
+                message += ": Directory " + dir + " doesn't exist";
+            } else if (!Files.exists(path)) {
+                message += ": File " + path + " doesn't exist";
+            } else if (!Files.isReadable(path)) {
+                message += ": File " + path + " is not readable";
+            } else if (Files.isDirectory(path)) {
+                message += ": Path " + path + " is a directory";
+            } else {
+                message += ": Unknown reason";
+            }
+            throw new SQLiteCantOpenDatabaseException(message, e);
         } finally {
             mRecentOperations.endOperation(cookie);
         }
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 91dae66..aa75f60 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -17,6 +17,7 @@
 package android.hardware.camera2;
 
 import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.PublicKey;
 import android.hardware.camera2.impl.SyntheticKey;
@@ -100,6 +101,8 @@
      *
      * @hide
      */
+    @UnsupportedAppUsage(publicAlternatives = "This method is exposed for native "
+                        + "{@code ACameraMetadata_fromCameraMetadata} in {@code libcamera2ndk}.")
     public long getNativeMetadataPtr() {
         if (mNativeInstance == null) {
             return 0;
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index 2041cfb..c87b827 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -75,7 +75,7 @@
     }
 
     /**
-     * Create an instance of the VpnManger with the given context.
+     * Create an instance of the VpnManager with the given context.
      *
      * <p>Internal only. Applications are expected to obtain an instance of the VpnManager via the
      * {@link Context.getSystemService()} method call.
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index b8e1aa8..be2de0e 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -1483,12 +1483,25 @@
         return mInteractiveCache.query(null);
     }
 
+
+    /**
+     * Returns {@code true} if this device supports rebooting userspace.
+     *
+     * <p>This method exists solely for the sake of re-using same logic between {@code PowerManager}
+     * and {@code PowerManagerService}.
+     *
+     * @hide
+     */
+    public static boolean isRebootingUserspaceSupportedImpl() {
+        return InitProperties.is_userspace_reboot_supported().orElse(false);
+    }
+
     /**
      * Returns {@code true} if this device supports rebooting userspace.
      */
     // TODO(b/138605180): add link to documentation once it's ready.
     public boolean isRebootingUserspaceSupported() {
-        return InitProperties.is_userspace_reboot_supported().orElse(false);
+        return isRebootingUserspaceSupportedImpl();
     }
 
     /**
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index b7b3c4f..5d2c9d1 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -607,6 +607,9 @@
      *                             started.
      * @param pkgDataInfoMap Map from related package names to private data directory
      *                       volume UUID and inode number.
+     * @param whitelistedDataInfoMap Map from whitelisted package names to private data directory
+     *                       volume UUID and inode number.
+     * @param bindMountAppsData whether zygote needs to mount CE and DE data.
      * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
      * @param zygoteArgs Additional arguments to supply to the zygote process.
      * @return An object that describes the result of the attempt to start the process.
@@ -631,13 +634,17 @@
                                            @Nullable long[] disabledCompatChanges,
                                            @Nullable Map<String, Pair<String, Long>>
                                                    pkgDataInfoMap,
+                                           @Nullable Map<String, Pair<String, Long>>
+                                                   whitelistedDataInfoMap,
+                                           boolean bindMountAppsData,
                                            boolean bindMountAppStorageDirs,
                                            @Nullable String[] zygoteArgs) {
         return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
                     zygotePolicyFlags, isTopApp, disabledCompatChanges,
-                    pkgDataInfoMap, bindMountAppStorageDirs, zygoteArgs);
+                    pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData,
+                    bindMountAppStorageDirs, zygoteArgs);
     }
 
     /** @hide */
@@ -661,7 +668,8 @@
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, packageName,
                     /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, /*isTopApp=*/ false,
-                disabledCompatChanges, /* pkgDataInfoMap */ null, false, zygoteArgs);
+                disabledCompatChanges, /* pkgDataInfoMap */ null,
+                /* whitelistedDataInfoMap */ null, false, false, zygoteArgs);
     }
 
     /**
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 5f3f14f..a4c99c0 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -333,6 +333,9 @@
      *                             started.
      * @param pkgDataInfoMap Map from related package names to private data directory
      *                       volume UUID and inode number.
+     * @param whitelistedDataInfoMap Map from whitelisted package names to private data directory
+     *                       volume UUID and inode number.
+     * @param bindMountAppsData whether zygote needs to mount CE and DE data.
      * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
      *
      * @param zygoteArgs Additional arguments to supply to the Zygote process.
@@ -355,6 +358,9 @@
                                                   @Nullable long[] disabledCompatChanges,
                                                   @Nullable Map<String, Pair<String, Long>>
                                                           pkgDataInfoMap,
+                                                  @Nullable Map<String, Pair<String, Long>>
+                                                          whitelistedDataInfoMap,
+                                                  boolean bindMountAppsData,
                                                   boolean bindMountAppStorageDirs,
                                                   @Nullable String[] zygoteArgs) {
         // TODO (chriswailes): Is there a better place to check this value?
@@ -367,7 +373,8 @@
                     runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                     abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,
                     packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges,
-                    pkgDataInfoMap, bindMountAppStorageDirs, zygoteArgs);
+                    pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData,
+                    bindMountAppStorageDirs, zygoteArgs);
         } catch (ZygoteStartFailedEx ex) {
             Log.e(LOG_TAG,
                     "Starting VM process through Zygote failed");
@@ -608,6 +615,9 @@
      * @param disabledCompatChanges a list of disabled compat changes for the process being started.
      * @param pkgDataInfoMap Map from related package names to private data directory volume UUID
      *                       and inode number.
+     * @param whitelistedDataInfoMap Map from whitelisted package names to private data directory
+     *                       volume UUID and inode number.
+     * @param bindMountAppsData whether zygote needs to mount CE and DE data.
      * @param bindMountAppStorageDirs whether zygote needs to mount Android/obb and Android/data.
      * @param extraArgs Additional arguments to supply to the zygote process.
      * @return An object that describes the result of the attempt to start the process.
@@ -631,6 +641,9 @@
                                                       @Nullable long[] disabledCompatChanges,
                                                       @Nullable Map<String, Pair<String, Long>>
                                                               pkgDataInfoMap,
+                                                      @Nullable Map<String, Pair<String, Long>>
+                                                              whitelistedDataInfoMap,
+                                                      boolean bindMountAppsData,
                                                       boolean bindMountAppStorageDirs,
                                                       @Nullable String[] extraArgs)
                                                       throws ZygoteStartFailedEx {
@@ -728,11 +741,33 @@
             }
             argsForZygote.add(sb.toString());
         }
+        if (whitelistedDataInfoMap != null && whitelistedDataInfoMap.size() > 0) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(Zygote.WHITELISTED_DATA_INFO_MAP);
+            sb.append("=");
+            boolean started = false;
+            for (Map.Entry<String, Pair<String, Long>> entry : whitelistedDataInfoMap.entrySet()) {
+                if (started) {
+                    sb.append(',');
+                }
+                started = true;
+                sb.append(entry.getKey());
+                sb.append(',');
+                sb.append(entry.getValue().first);
+                sb.append(',');
+                sb.append(entry.getValue().second);
+            }
+            argsForZygote.add(sb.toString());
+        }
 
         if (bindMountAppStorageDirs) {
             argsForZygote.add(Zygote.BIND_MOUNT_APP_STORAGE_DIRS);
         }
 
+        if (bindMountAppsData) {
+            argsForZygote.add(Zygote.BIND_MOUNT_APP_DATA_DIRS);
+        }
+
         if (disabledCompatChanges != null && disabledCompatChanges.length > 0) {
             StringBuilder sb = new StringBuilder();
             sb.append("--disabled-compat-changes=");
@@ -1291,6 +1326,7 @@
                     true /* startChildZygote */, null /* packageName */,
                     ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS /* zygotePolicyFlags */, false /* isTopApp */,
                     null /* disabledCompatChanges */, null /* pkgDataInfoMap */,
+                    null /* whitelistedDataInfoMap */, false /* bindMountAppsData*/,
                     /* bindMountAppStorageDirs */ false, extraArgs);
 
         } catch (ZygoteStartFailedEx ex) {
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
index d8308c7..2dbaea8 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -38,13 +38,6 @@
     int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
 
     /**
-     * Changes storage params. Returns 0 on success, and -errno on failure.
-     * Use enableReadLogs to switch pages read logs reporting on and off.
-     * Returns 0 on success, and - errno on failure: permission check or remount.
-     */
-    int setStorageParams(int storageId, boolean enableReadLogs);
-
-    /**
      * Bind-mounts a path under a storage to a full path. Can be permanent or temporary.
      */
     const int BIND_TEMPORARY = 0;
diff --git a/core/java/android/os/incremental/IIncrementalServiceConnector.aidl b/core/java/android/os/incremental/IIncrementalServiceConnector.aidl
new file mode 100644
index 0000000..5800ecf
--- /dev/null
+++ b/core/java/android/os/incremental/IIncrementalServiceConnector.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.incremental;
+
+/** @hide */
+interface IIncrementalServiceConnector {
+    /**
+     * Changes storage params. Returns 0 on success, and -errno on failure.
+     * Use enableReadLogs to switch pages read logs reporting on and off.
+     * Returns 0 on success, and - errno on failure: permission check or remount.
+     */
+    int setStorageParams(boolean enableReadLogs);
+}
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 5f01408..35518db 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -19,13 +19,11 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.pm.DataLoaderParams;
 import android.content.pm.IDataLoaderStatusListener;
 import android.os.RemoteException;
-import android.system.ErrnoException;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -321,23 +319,6 @@
         return nativeUnsafeGetFileSignature(path);
     }
 
-    /**
-     * Sets storage parameters.
-     *
-     * @param enableReadLogs - enables or disables read logs. Caller has to have a permission.
-     */
-    @RequiresPermission(android.Manifest.permission.LOADER_USAGE_STATS)
-    public void setStorageParams(int storageId, boolean enableReadLogs) throws ErrnoException {
-        try {
-            int res = mService.setStorageParams(storageId, enableReadLogs);
-            if (res < 0) {
-                throw new ErrnoException("setStorageParams", -res);
-            }
-        } catch (RemoteException e) {
-            e.rethrowFromSystemServer();
-        }
-    }
-
     /* Native methods */
     private static native boolean nativeIsEnabled();
     private static native boolean nativeIsIncrementalPath(@NonNull String path);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 920302c..5418833 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1870,6 +1870,24 @@
     public static final String EXTRA_APP_UID = "app_uid";
 
     /**
+     * Activity Action: Show power menu settings.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_POWER_MENU_SETTINGS =
+            "android.settings.ACTION_POWER_MENU_SETTINGS";
+
+    /**
+     * Activity Action: Show controls settings.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_DEVICE_CONTROLS_SETTINGS =
+            "android.settings.ACTION_DEVICE_CONTROLS_SETTINGS";
+
+    /**
      * Activity Action: Show a dialog with disabled by policy message.
      * <p> If an user action is disabled by policy, this dialog can be triggered to let
      * the user know about this.
diff --git a/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl b/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
index dd434b4..bf0bb9e 100644
--- a/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
+++ b/core/java/android/service/autofill/IInlineSuggestionRenderService.aidl
@@ -17,6 +17,7 @@
 package android.service.autofill;
 
 import android.os.IBinder;
+import android.os.RemoteCallback;
 import android.service.autofill.IInlineSuggestionUiCallback;
 import android.service.autofill.InlinePresentation;
 
@@ -29,4 +30,5 @@
     void renderSuggestion(in IInlineSuggestionUiCallback callback,
                           in InlinePresentation presentation, int width, int height,
                           in IBinder hostInputToken, int displayId);
+    void getInlineSuggestionsRendererInfo(in RemoteCallback callback);
 }
diff --git a/core/java/android/service/autofill/InlineSuggestionRenderService.java b/core/java/android/service/autofill/InlineSuggestionRenderService.java
index cba6608..e3ed21f 100644
--- a/core/java/android/service/autofill/InlineSuggestionRenderService.java
+++ b/core/java/android/service/autofill/InlineSuggestionRenderService.java
@@ -30,6 +30,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.Display;
@@ -128,6 +129,11 @@
         }
     }
 
+    private void handleGetInlineSuggestionsRendererInfo(@NonNull RemoteCallback callback) {
+        final Bundle rendererInfo = onGetInlineSuggestionsRendererInfo();
+        callback.sendResult(rendererInfo);
+    }
+
     private void sendResult(@NonNull IInlineSuggestionUiCallback callback,
             @Nullable SurfaceControlViewHost.SurfacePackage surface) {
         try {
@@ -151,6 +157,13 @@
                             InlineSuggestionRenderService.this, callback, presentation,
                             width, height, hostInputToken, displayId));
                 }
+
+                @Override
+                public void getInlineSuggestionsRendererInfo(@NonNull RemoteCallback callback) {
+                    mHandler.sendMessage(obtainMessage(
+                            InlineSuggestionRenderService::handleGetInlineSuggestionsRendererInfo,
+                            InlineSuggestionRenderService.this, callback));
+                }
             }.asBinder();
         }
 
diff --git a/core/java/android/service/contentcapture/ContentCaptureService.java b/core/java/android/service/contentcapture/ContentCaptureService.java
index ef55f06..46cb65b 100644
--- a/core/java/android/service/contentcapture/ContentCaptureService.java
+++ b/core/java/android/service/contentcapture/ContentCaptureService.java
@@ -747,7 +747,7 @@
         void initializeForDelegate(DataShareReadAdapterDelegate delegate,
                 DataShareReadAdapter adapter, Executor executor) {
             mDataShareReadAdapterHardReferences.put(delegate, adapter);
-            mExecutorHardReferences.remove(delegate, executor);
+            mExecutorHardReferences.put(delegate, executor);
         }
 
         Executor getExecutor(DataShareReadAdapterDelegate delegate) {
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index 05877a5..60373ac 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -21,7 +21,6 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.app.Service;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.DataLoaderParams;
 import android.content.pm.DataLoaderParamsParcel;
@@ -32,11 +31,11 @@
 import android.content.pm.InstallationFileParcel;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
-import android.os.incremental.IncrementalManager;
-import android.system.ErrnoException;
 import android.util.ExceptionUtils;
 import android.util.Slog;
 
+import libcore.io.IoUtils;
+
 import java.io.IOException;
 import java.util.Collection;
 
@@ -118,22 +117,10 @@
                 destroy(id);
                 throw new RuntimeException(ex);
             } finally {
-                // Closing FDs.
                 if (control.incremental != null) {
-                    if (control.incremental.cmd != null) {
-                        try {
-                            control.incremental.cmd.close();
-                        } catch (IOException e) {
-                            Slog.e(TAG, "Failed to close IncFs CMD file descriptor " + e);
-                        }
-                    }
-                    if (control.incremental.log != null) {
-                        try {
-                            control.incremental.log.close();
-                        } catch (IOException e) {
-                            Slog.e(TAG, "Failed to close IncFs LOG file descriptor " + e);
-                        }
-                    }
+                    IoUtils.closeQuietly(control.incremental.cmd);
+                    IoUtils.closeQuietly(control.incremental.pendingReads);
+                    IoUtils.closeQuietly(control.incremental.log);
                 }
             }
         }
@@ -211,25 +198,6 @@
         private final long mNativeInstance;
     }
 
-    /* Used by native FileSystemConnector. */
-    private boolean setStorageParams(int storageId, boolean enableReadLogs) {
-        IncrementalManager incrementalManager = (IncrementalManager) getSystemService(
-                Context.INCREMENTAL_SERVICE);
-        if (incrementalManager == null) {
-            Slog.e(TAG, "Failed to obtain incrementalManager: " + storageId);
-            return false;
-        }
-        try {
-            // This has to be done directly in incrementalManager as the storage
-            // might be missing still.
-            incrementalManager.setStorageParams(storageId, enableReadLogs);
-        } catch (ErrnoException e) {
-            Slog.e(TAG, "Failed to set params for storage: " + storageId, e);
-            return false;
-        }
-        return true;
-    }
-
     /* Native methods */
     private native boolean nativeCreateDataLoader(int storageId,
             @NonNull FileSystemControlParcel control,
diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl
index a60a5cc..983ab2e 100644
--- a/core/java/android/view/IRecentsAnimationController.aidl
+++ b/core/java/android/view/IRecentsAnimationController.aidl
@@ -114,4 +114,16 @@
      * animation is cancelled through fail safe mechanism.
      */
     void setWillFinishToHome(boolean willFinishToHome);
+
+    /**
+     * Stops controlling a task that is currently controlled by this recents animation.
+     *
+     * This method should be called when a task that has been received via {@link #onAnimationStart}
+     * or {@link #onTaskAppeared} is no longer needed.  After calling this method, the task will
+     * either disappear from the screen, or jump to its final position in case it was the top task.
+     *
+     * @param taskId Id of the Task target to remove
+     * @return {@code true} when target removed successfully, {@code false} otherwise.
+     */
+    boolean removeTask(int taskId);
 }
diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl
index 6eb90fc..925786f 100644
--- a/core/java/android/view/IRecentsAnimationRunner.aidl
+++ b/core/java/android/view/IRecentsAnimationRunner.aidl
@@ -56,4 +56,10 @@
     void onAnimationStart(in IRecentsAnimationController controller,
             in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers,
             in Rect homeContentInsets, in Rect minimizedHomeBounds) = 2;
+
+    /**
+     * Called when the task of an activity that has been started while the recents animation
+     * was running becomes ready for control.
+     */
+    void onTaskAppeared(in RemoteAnimationTarget app) = 3;
 }
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 84ac90b..b0bacb9 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -124,13 +124,20 @@
      * @param type Window type to be used with this token.
      * @param options A bundle used to pass window-related options.
      * @param displayId The ID of the display where this token should be added.
-     * @param packageName The name of package to request to add window token.
+     * @param packageName The name of package to request to add window token. Could be {@code null}
+     *                    if callers holds the MANAGE_APP_TOKENS permission.
      * @return {@link WindowManagerGlobal#ADD_OKAY} if the addition was successful, an error code
      *         otherwise.
      */
     int addWindowTokenWithOptions(IBinder token, int type, int displayId, in Bundle options,
             String packageName);
     void addWindowToken(IBinder token, int type, int displayId);
+    /**
+     * Remove window token on a specific display.
+     *
+     * @param token Token to be removed
+     * @displayId The ID of the display where this token should be removed.
+     */
     void removeWindowToken(IBinder token, int displayId);
     void prepareAppTransition(int transit, boolean alwaysKeepCurrent);
 
@@ -735,4 +742,11 @@
      * Called to show global actions.
      */
     void showGlobalActions();
+
+    /**
+     * Sets layer tracing flags for SurfaceFlingerTrace. 
+     *
+     * @param flags see definition in SurfaceTracing.cpp
+     */
+    void setLayerTracingFlags(int flags);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 81bfcb0..a0527b7 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -48,6 +48,11 @@
             out Rect outContentInsets, out Rect outStableInsets,
             out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel,
             out InsetsState insetsState, out InsetsSourceControl[] activeControls);
+    int addToDisplayAsUser(IWindow window, int seq, in WindowManager.LayoutParams attrs,
+                in int viewVisibility, in int layerStackId, in int userId,
+                out Rect outFrame, out Rect outContentInsets, out Rect outStableInsets,
+                out DisplayCutout.ParcelableWrapper displayCutout, out InputChannel outInputChannel,
+                out InsetsState insetsState, out InsetsSourceControl[] activeControls);
     int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
             in int viewVisibility, in int layerStackId, out Rect outContentInsets,
             out Rect outStableInsets, out InsetsState insetsState);
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 69bab4d..6cb9374 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -91,6 +91,8 @@
         if (mSourceControl == control) {
             return;
         }
+        SurfaceControl oldLeash = mSourceControl != null ? mSourceControl.getLeash() : null;
+
         final InsetsSourceControl lastControl = mSourceControl;
         mSourceControl = control;
 
@@ -116,6 +118,12 @@
                 // However make sure that the leash visibility is still up to date.
                 if (applyLocalVisibilityOverride()) {
                     mController.notifyVisibilityChanged();
+                }
+
+                // If we have a new leash, make sure visibility is up-to-date, even though we
+                // didn't want to run an animation above.
+                SurfaceControl newLeash = mSourceControl.getLeash();
+                if (oldLeash == null || newLeash == null || !oldLeash.isSameSurface(newLeash)) {
                     applyHiddenToControl();
                 }
             }
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index f3ec65f..e001b66 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -44,8 +44,7 @@
     public InsetsSourceControl(InsetsSourceControl other) {
         mType = other.mType;
         if (other.mLeash != null) {
-            mLeash = new SurfaceControl();
-            mLeash.copyFrom(other.mLeash);
+            mLeash = new SurfaceControl(other.mLeash);
         } else {
             mLeash = null;
         }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b5f9df7..1086774 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -216,6 +216,7 @@
 
     private static native void nativeSetFrameRate(
             long transactionObj, long nativeObject, float frameRate, int compatibility);
+    private static native long nativeGetHandle(long nativeObject);
 
     private static native long nativeAcquireFrameRateFlexibilityToken();
     private static native void nativeReleaseFrameRateFlexibilityToken(long token);
@@ -226,6 +227,7 @@
      * @hide
      */
     public long mNativeObject;
+    private long mNativeHandle;
 
     // TODO: Move this to native.
     private final Object mSizeLock = new Object();
@@ -428,12 +430,13 @@
             mCloseGuard.open("release");
         }
         mNativeObject = nativeObject;
+        mNativeHandle = mNativeObject != 0 ? nativeGetHandle(nativeObject) : 0;
     }
 
     /**
      * @hide
      */
-    public void copyFrom(SurfaceControl other) {
+    public void copyFrom(@NonNull SurfaceControl other) {
         mName = other.mName;
         mWidth = other.mWidth;
         mHeight = other.mHeight;
@@ -853,23 +856,19 @@
             throw new OutOfResourcesException(
                     "Couldn't allocate SurfaceControl native object");
         }
-
+        mNativeHandle = nativeGetHandle(mNativeObject);
         mCloseGuard.open("release");
     }
 
-    /** This is a transfer constructor, useful for transferring a live SurfaceControl native
-     * object to another Java wrapper which could have some different behavior, e.g.
-     * event logging.
+    /**
+     * Copy constructor. Creates a new native object pointing to the same surface as {@code other}.
+     *
+     * @param other The object to copy the surface from.
      * @hide
      */
-    public SurfaceControl(SurfaceControl other) {
-        mName = other.mName;
-        mWidth = other.mWidth;
-        mHeight = other.mHeight;
-        mNativeObject = other.mNativeObject;
-        other.mCloseGuard.close();
-        other.mNativeObject = 0;
-        mCloseGuard.open("release");
+    @TestApi
+    public SurfaceControl(@NonNull SurfaceControl other) {
+        copyFrom(other);
     }
 
     private SurfaceControl(Parcel in) {
@@ -921,6 +920,18 @@
     }
 
     /**
+     * Checks whether two {@link SurfaceControl} objects represent the same surface.
+     *
+     * @param other The other object to check
+     * @return {@code true} if these two {@link SurfaceControl} objects represent the same surface.
+     * @hide
+     */
+    @TestApi
+    public boolean isSameSurface(@NonNull SurfaceControl other) {
+        return other.mNativeHandle == mNativeHandle;
+    }
+
+    /**
      * Write to a protocol buffer output stream. Protocol buffer message definition is at {@link
      * android.view.SurfaceControlProto}.
      *
@@ -977,6 +988,7 @@
         if (mNativeObject != 0) {
             nativeRelease(mNativeObject);
             mNativeObject = 0;
+            mNativeHandle = 0;
             mCloseGuard.close();
         }
     }
@@ -2876,7 +2888,8 @@
     /**
      * Acquire a frame rate flexibility token, which allows surface flinger to freely switch display
      * frame rates. This is used by CTS tests to put the device in a consistent state. See
-     * ISurfaceComposer::acquireFrameRateFlexibilityToken().
+     * ISurfaceComposer::acquireFrameRateFlexibilityToken(). The caller must have the
+     * ACCESS_SURFACE_FLINGER permission, or else the call will fail, returning 0.
      * @hide
      */
     @TestApi
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index da18608..8abe72f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14684,17 +14684,19 @@
                 }
             }
         }
-        if (isAccessibilityPane()) {
-            if (isVisible != oldVisible) {
+
+        if (isVisible != oldVisible) {
+            if (isAccessibilityPane()) {
                 notifyViewAccessibilityStateChangedIfNeeded(isVisible
                         ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED
                         : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED);
             }
-        }
 
-        notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
-        if (!getSystemGestureExclusionRects().isEmpty() && isVisible != oldVisible) {
-            postUpdateSystemGestureExclusionRects();
+            notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
+
+            if (!getSystemGestureExclusionRects().isEmpty()) {
+                postUpdateSystemGestureExclusionRects();
+            }
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 750b1ed..ed1edc3 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -111,6 +111,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.sysprop.DisplayProperties;
 import android.util.AndroidRuntimeException;
 import android.util.DisplayMetrics;
@@ -893,6 +894,14 @@
      * We have one child
      */
     public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
+        setView(view, attrs, panelParentView, UserHandle.myUserId());
+    }
+
+    /**
+     * We have one child
+     */
+    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
+            int userId) {
         synchronized (this) {
             if (mView == null) {
                 mView = view;
@@ -1001,8 +1010,8 @@
                     mAttachInfo.mRecomputeGlobalAttributes = true;
                     collectViewAttributes();
                     adjustLayoutParamsForCompatibility(mWindowAttributes);
-                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
-                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
+                    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
+                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                             mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                             mAttachInfo.mDisplayCutout, inputChannel,
                             mTempInsets, mTempControls);
@@ -1075,6 +1084,9 @@
                             throw new WindowManager.InvalidDisplayException("Unable to add window "
                                     + mWindow + " -- the specified window type "
                                     + mWindowAttributes.type + " is not valid");
+                        case WindowManagerGlobal.ADD_INVALID_USER:
+                            throw new WindowManager.BadTokenException("Unable to add Window "
+                                    + mWindow + " -- requested userId is not valid");
                     }
                     throw new RuntimeException(
                             "Unable to add window -- unknown error code " + res);
@@ -3017,6 +3029,10 @@
         if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
             reportNextDraw();
         }
+        if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_BLAST_SYNC) != 0) {
+            reportNextDraw();
+            setUseBLASTSyncTransaction();
+        }
 
         boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
 
@@ -3725,7 +3741,7 @@
             if (needFrameCompleteCallback) {
                 final Handler handler = mAttachInfo.mHandler;
                 mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> {
-                        finishBLASTSync();
+                        finishBLASTSync(!reportNextDraw);
                         handler.postAtFrontOfQueue(() -> {
                             if (reportNextDraw) {
                                 // TODO: Use the frame number
@@ -3759,7 +3775,7 @@
             if (usingAsyncReport && !canUseAsync) {
                 mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
                 usingAsyncReport = false;
-                finishBLASTSync();
+                finishBLASTSync(true /* apply */);
             }
         } finally {
             mIsDrawing = false;
@@ -9576,10 +9592,15 @@
         mNextDrawUseBLASTSyncTransaction = true;
     }
 
-    private void finishBLASTSync() {
+    private void finishBLASTSync(boolean apply) {
         if (mNextReportConsumeBLAST) {
             mNextReportConsumeBLAST = false;
-            mRtBLASTSyncTransaction.apply();
+
+            if (apply) {
+                mRtBLASTSyncTransaction.apply();
+            } else {
+                mSurfaceChangedTransaction.merge(mRtBLASTSyncTransaction);
+            }
         }
     }
 
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 410d9af..fba6a55 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -102,6 +102,14 @@
     public static final int RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS = 0x40;
 
     /**
+     * This flag indicates the client should not directly submit it's next frame,
+     * but instead should pass it in the postDrawTransaction of
+     * {@link WindowManagerService#finishDrawing}. This is used by the WM
+     * BLASTSyncEngine to synchronize rendering of multiple windows.
+     */
+    public static final int RELAYOUT_RES_BLAST_SYNC = 0x80;
+
+    /**
      * Flag for relayout: the client will be later giving
      * internal insets; as a result, the window will not impact other window
      * layouts until the insets are given.
@@ -136,6 +144,7 @@
     public static final int ADD_PERMISSION_DENIED = -8;
     public static final int ADD_INVALID_DISPLAY = -9;
     public static final int ADD_INVALID_TYPE = -10;
+    public static final int ADD_INVALID_USER = -11;
 
     @UnsupportedAppUsage
     private static WindowManagerGlobal sDefaultWindowManager;
@@ -317,7 +326,7 @@
     }
 
     public void addView(View view, ViewGroup.LayoutParams params,
-            Display display, Window parentWindow) {
+            Display display, Window parentWindow, int userId) {
         if (view == null) {
             throw new IllegalArgumentException("view must not be null");
         }
@@ -394,7 +403,7 @@
 
             // do this last because it fires off messages to start doing things
             try {
-                root.setView(view, wparams, panelParentView);
+                root.setView(view, wparams, panelParentView, userId);
             } catch (RuntimeException e) {
                 // BadTokenException or InvalidDisplayException, clean up.
                 if (index >= 0) {
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 316a5f2..2975d5e 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -104,7 +104,8 @@
     @Override
     public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
         applyDefaultToken(params);
-        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow);
+        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
+                mContext.getUserId());
     }
 
     @Override
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 39ed401..ec51301 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -130,6 +130,20 @@
         return WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
     }
 
+    /**
+     * IWindowSession implementation. Currently this class doesn't need to support for multi-user.
+     */
+    @Override
+    public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
+            int viewVisibility, int displayId, int userId, Rect outFrame,
+            Rect outContentInsets, Rect outStableInsets,
+            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
+        return addToDisplay(window, seq, attrs, viewVisibility, displayId,
+                outFrame, outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
+                outInsetsState, outActiveControls);
+    }
+
     @Override
     public int addToDisplayWithoutInputChannel(android.view.IWindow window, int seq,
             android.view.WindowManager.LayoutParams attrs, int viewVisibility, int layerStackId,
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index edc6b12..4519aef 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -832,7 +832,7 @@
         void initializeForDelegate(DataShareAdapterDelegate delegate, DataShareWriteAdapter adapter,
                 Executor executor) {
             mWriteAdapterHardReferences.put(delegate, adapter);
-            mExecutorHardReferences.remove(delegate, executor);
+            mExecutorHardReferences.put(delegate, executor);
         }
 
         Executor getExecutor(DataShareAdapterDelegate delegate) {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 482d5b25..71dd665 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1968,6 +1968,38 @@
         return true;
     }
 
+    /**
+     * An empty method only to avoid crashes of apps that call this method via reflection and do not
+     * handle {@link NoSuchMethodException} in a graceful manner.
+     *
+     * @deprecated This is an empty method.  No framework method must call this method.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage(trackingBug = 37122102, maxTargetSdk = Build.VERSION_CODES.Q,
+            publicAlternatives = "{@code androidx.activity.ComponentActivity}")
+    public void windowDismissed(IBinder appWindowToken) {
+        // Intentionally empty.
+        //
+        // It seems that some applications call this method via reflection to null clear the
+        // following fields that used to exist in InputMethodManager:
+        //  * InputMethodManager#mCurRootView
+        //  * InputMethodManager#mServedView
+        //  * InputMethodManager#mNextServedView
+        // so that these objects can be garbage-collected when an Activity gets dismissed.
+        //
+        // It is indeed true that older versions of InputMethodManager had issues that prevented
+        // these fields from being null-cleared when it should have been, but the understanding of
+        // the engineering team is that all known issues have already been fixed as of Android 10.
+        //
+        // For older devices, developers can work around the object leaks by using
+        // androidx.activity.ComponentActivity.
+        // See https://issuetracker.google.com/u/1/issues/37122102 for details.
+        //
+        // If you believe InputMethodManager is leaking objects in API 24 or any later version,
+        // please file a bug at https://issuetracker.google.com/issues/new?component=192705.
+    }
+
     private int getStartInputFlags(View focusedView, int startInputFlags) {
         startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS;
         if (focusedView.onCheckIsTextEditor()) {
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index 842ba29..6ad5cb9 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -27,8 +27,6 @@
 import android.os.Parcelable;
 import android.text.SpannedString;
 
-import com.android.internal.util.Preconditions;
-
 import java.lang.annotation.Retention;
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
@@ -491,7 +489,11 @@
              */
             @NonNull
             public Builder setMaxSuggestions(@IntRange(from = -1) int maxSuggestions) {
-                mMaxSuggestions = Preconditions.checkArgumentNonnegative(maxSuggestions);
+                if (maxSuggestions < -1) {
+                    throw new IllegalArgumentException("maxSuggestions has to be greater than or "
+                            + "equal to -1.");
+                }
+                mMaxSuggestions = maxSuggestions;
                 return this;
             }
 
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index eee222b..6ae70b7 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -33,8 +33,8 @@
     public static final int FEATURE_SYSTEM_FIRST = 0;
     // The Root display area on a display
     public static final int FEATURE_ROOT = FEATURE_SYSTEM_FIRST;
-    // Display area hosting the task container.
-    public static final int FEATURE_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1;
+    // Display area hosting the default task container.
+    public static final int FEATURE_DEFAULT_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1;
     // Display area hosting non-activity window tokens.
     public static final int FEATURE_WINDOW_TOKENS = FEATURE_SYSTEM_FIRST + 2;
 
diff --git a/core/java/android/window/TaskOrganizerTaskEmbedder.java b/core/java/android/window/TaskOrganizerTaskEmbedder.java
index 2091c93..39a0101 100644
--- a/core/java/android/window/TaskOrganizerTaskEmbedder.java
+++ b/core/java/android/window/TaskOrganizerTaskEmbedder.java
@@ -254,7 +254,9 @@
             mTaskToken = taskInfo.token;
             mTaskLeash = mTaskToken.getLeash();
             mTransaction.reparent(mTaskLeash, mSurfaceControl)
-                    .show(mSurfaceControl).apply();
+                    .show(mTaskLeash)
+                    .show(mSurfaceControl)
+                    .apply();
             if (mPendingNotifyBoundsChanged) {
                 // TODO: Either defer show or hide and synchronize show with the resize
                 notifyBoundsChanged();
diff --git a/core/java/android/window/VirtualDisplayTaskEmbedder.java b/core/java/android/window/VirtualDisplayTaskEmbedder.java
index 1afbfeb..0f26d5d 100644
--- a/core/java/android/window/VirtualDisplayTaskEmbedder.java
+++ b/core/java/android/window/VirtualDisplayTaskEmbedder.java
@@ -16,7 +16,6 @@
 
 package android.window;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
@@ -247,7 +246,6 @@
     protected ActivityOptions prepareActivityOptions(ActivityOptions options) {
         options = super.prepareActivityOptions(options);
         options.setLaunchDisplayId(getDisplayId());
-        options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
         return options;
     }
 
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index ff03f1a..505a05e 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -205,9 +205,15 @@
     /** List of packages with the same uid, and its app data info: volume uuid and inode. */
     public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map";
 
+    /** List of whitelisted packages and its app data info: volume uuid and inode. */
+    public static final String WHITELISTED_DATA_INFO_MAP = "--whitelisted-data-info-map";
+
     /** Bind mount app storage dirs to lower fs not via fuse */
     public static final String BIND_MOUNT_APP_STORAGE_DIRS = "--bind-mount-storage-dirs";
 
+    /** Bind mount app storage dirs to lower fs not via fuse */
+    public static final String BIND_MOUNT_APP_DATA_DIRS = "--bind-mount-data-dirs";
+
     /**
      * An extraArg passed when a zygote process is forking a child-zygote, specifying a name
      * in the abstract socket namespace. This socket name is what the new child zygote
@@ -313,6 +319,8 @@
      * @param isTopApp true if the process is for top (high priority) application.
      * @param pkgDataInfoList A list that stores related packages and its app data
      * info: volume uuid and inode.
+     * @param whitelistedDataInfoList Like pkgDataInfoList, but it's for whitelisted apps.
+     * @param bindMountAppDataDirs  True if the zygote needs to mount data dirs.
      * @param bindMountAppStorageDirs  True if the zygote needs to mount storage dirs.
      *
      * @return 0 if this is the child, pid of the child
@@ -321,13 +329,15 @@
     static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
             int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
-            boolean isTopApp, String[] pkgDataInfoList, boolean bindMountAppStorageDirs) {
+            boolean isTopApp, String[] pkgDataInfoList, String[] whitelistedDataInfoList,
+            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
         ZygoteHooks.preFork();
 
         int pid = nativeForkAndSpecialize(
                 uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
                 fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
-                pkgDataInfoList, bindMountAppStorageDirs);
+                pkgDataInfoList, whitelistedDataInfoList, bindMountAppDataDirs,
+                bindMountAppStorageDirs);
         if (pid == 0) {
             // Note that this event ends at the end of handleChildProc,
             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
@@ -344,6 +354,7 @@
             int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
             int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet,
             String appDataDir, boolean isTopApp, String[] pkgDataInfoList,
+            String[] whitelistedDataInfoList, boolean bindMountAppDataDirs,
             boolean bindMountAppStorageDirs);
 
     /**
@@ -371,15 +382,19 @@
      * volume uuid and CE dir inode. For example, pkgDataInfoList = [app_a_pkg_name,
      * app_a_data_volume_uuid, app_a_ce_inode, app_b_pkg_name, app_b_data_volume_uuid,
      * app_b_ce_inode, ...];
+     * @param whitelistedDataInfoList Like pkgDataInfoList, but it's for whitelisted apps.
+     * @param bindMountAppDataDirs  True if the zygote needs to mount data dirs.
      * @param bindMountAppStorageDirs  True if the zygote needs to mount storage dirs.
      */
     private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, int mountExternal, String seInfo, String niceName,
             boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
-            String[] pkgDataInfoList, boolean bindMountAppStorageDirs) {
+            String[] pkgDataInfoList, String[] whitelistedDataInfoList,
+            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
         nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo,
                 niceName, startChildZygote, instructionSet, appDataDir, isTopApp,
-                pkgDataInfoList, bindMountAppStorageDirs);
+                pkgDataInfoList, whitelistedDataInfoList,
+                bindMountAppDataDirs, bindMountAppStorageDirs);
 
         // Note that this event ends at the end of handleChildProc.
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
@@ -399,7 +414,8 @@
     private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids,
             int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName,
             boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp,
-            String[] pkgDataInfoList, boolean bindMountAppStorageDirs);
+            String[] pkgDataInfoList, String[] whitelistedDataInfoList,
+            boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs);
 
     /**
      * Called to do any initialization before starting an application.
@@ -724,7 +740,8 @@
                                  args.mRuntimeFlags, rlimits, args.mMountExternal,
                                  args.mSeInfo, args.mNiceName, args.mStartChildZygote,
                                  args.mInstructionSet, args.mAppDataDir, args.mIsTopApp,
-                                 args.mPkgDataInfoList, args.mBindMountAppStorageDirs);
+                                 args.mPkgDataInfoList, args.mWhitelistedDataInfoList,
+                                 args.mBindMountAppDataDirs, args.mBindMountAppStorageDirs);
 
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
 
@@ -1060,4 +1077,11 @@
      */
     @FastNative
     public static native int nativeParseSigChld(byte[] in, int length, int[] out);
+
+    /**
+     * Returns whether the kernel supports tagged pointers. Present in the
+     * Android Common Kernel from 4.14 and up. By default, you should prefer
+     * fully-feature Memory Tagging, rather than the static Tagged Pointers.
+     */
+    public static native boolean nativeSupportsTaggedPointers();
 }
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index 1a63765..94c1f71 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -227,11 +227,22 @@
     String[] mPkgDataInfoList;
 
     /**
+     * A list that stores all whitelisted app data info: volume uuid and inode.
+     * Null if it does need to do app data isolation.
+     */
+    String[] mWhitelistedDataInfoList;
+
+    /**
      * @see Zygote#BIND_MOUNT_APP_STORAGE_DIRS
      */
     boolean mBindMountAppStorageDirs;
 
     /**
+     * @see Zygote#BIND_MOUNT_APP_DATA_DIRS
+     */
+    boolean mBindMountAppDataDirs;
+
+    /**
      * Constructs instance and parses args
      *
      * @param args zygote command-line args
@@ -452,8 +463,12 @@
                 }
             } else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) {
                 mPkgDataInfoList = getAssignmentList(arg);
+            } else if (arg.startsWith(Zygote.WHITELISTED_DATA_INFO_MAP)) {
+                mWhitelistedDataInfoList = getAssignmentList(arg);
             } else if (arg.equals(Zygote.BIND_MOUNT_APP_STORAGE_DIRS)) {
                 mBindMountAppStorageDirs = true;
+            } else if (arg.equals(Zygote.BIND_MOUNT_APP_DATA_DIRS)) {
+                mBindMountAppDataDirs = true;
             } else {
                 break;
             }
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index bc8dfd4..e6a3029 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -258,7 +258,8 @@
                 parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo,
                 parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
                 parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mIsTopApp,
-                parsedArgs.mPkgDataInfoList, parsedArgs.mBindMountAppStorageDirs);
+                parsedArgs.mPkgDataInfoList, parsedArgs.mWhitelistedDataInfoList,
+                parsedArgs.mBindMountAppDataDirs, parsedArgs.mBindMountAppStorageDirs);
 
         try {
             if (pid == 0) {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index ec1f516..c2b13c9 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -757,9 +757,11 @@
             Zygote.applyDebuggerSystemProperty(parsedArgs);
             Zygote.applyInvokeWithSystemProperty(parsedArgs);
 
-            /* Enable pointer tagging in the system server unconditionally. Hardware support for
-             * this is present in all ARMv8 CPUs; this flag has no effect on other platforms. */
-            parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+            if (Zygote.nativeSupportsTaggedPointers()) {
+                /* Enable pointer tagging in the system server. Hardware support for this is present
+                 * in all ARMv8 CPUs. */
+                parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+            }
 
             /* Enable gwp-asan on the system server with a small probability. This is the same
              * policy as applied to native processes and system apps. */
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 71cf5ca..b6c58e1 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -68,7 +68,6 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.InsetDrawable;
 import android.graphics.drawable.LayerDrawable;
-import android.os.Build.VERSION_CODES;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Pair;
@@ -120,7 +119,6 @@
 import com.android.internal.widget.FloatingToolbar;
 
 import java.util.List;
-import java.util.function.Function;
 
 /** @hide */
 public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
@@ -283,11 +281,6 @@
     private Insets mLastBackgroundInsets = Insets.NONE;
     private boolean mDrawLegacyNavigationBarBackground;
 
-    /**
-     * Whether the app targets an SDK that uses the new insets APIs.
-     */
-    private boolean mUseNewInsetsApi;
-
     private PendingInsetsController mPendingInsetsController = new PendingInsetsController();
 
     DecorView(Context context, int featureId, PhoneWindow window,
@@ -319,7 +312,6 @@
         initResizingPaints();
 
         mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK);
-        mUseNewInsetsApi = context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.R;
     }
 
     void setBackgroundFallback(@Nullable Drawable fallbackDrawable) {
@@ -1189,23 +1181,23 @@
         // these flags wouldn't make the window draw behind the navigation bar, unless
         // LAYOUT_HIDE_NAVIGATION was set.
         //
-        // Note: Once the app targets R+, we no longer do this logic because we can't rely on
-        // SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION to indicate whether the app wants to handle it by
-        // themselves.
+        // Note: Once the app uses the R+ Window.setDecorFitsSystemWindows(false) API we no longer
+        // consume insets because they might no longer set SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION.
         boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
                 || !(controller == null || controller.isRequestedVisible(ITYPE_NAVIGATION_BAR));
+        boolean decorFitsSystemWindows = mWindow.mDecorFitsSystemWindows;
         boolean forceConsumingNavBar = (mForceWindowDrawsBarBackgrounds
                         && (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
                         && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
+                        && decorFitsSystemWindows
                         && !hideNavigation)
                 || (mLastShouldAlwaysConsumeSystemBars && hideNavigation);
 
         boolean consumingNavBar =
                 ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
                         && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
-                        && !hideNavigation
-                        // TODO IME wrap_content windows need to have margin to work properly
-                        && (!mUseNewInsetsApi || isImeWindow))
+                        && decorFitsSystemWindows
+                        && !hideNavigation)
                 || forceConsumingNavBar;
 
         // If we didn't request fullscreen layout, but we still got it because of the
@@ -1216,6 +1208,7 @@
                 || (attrs.flags & FLAG_FULLSCREEN) != 0
                 || !(controller == null || controller.isRequestedVisible(ITYPE_STATUS_BAR));
         boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
+                && decorFitsSystemWindows
                 && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
                 && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
                 && mForceWindowDrawsBarBackgrounds
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 138d0dd..25c114f 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -343,8 +343,7 @@
     /** @see ViewRootImpl#mActivityConfigCallback */
     private ActivityConfigCallback mActivityConfigCallback;
 
-    private OnContentApplyWindowInsetsListener mPendingOnContentApplyWindowInsetsListener =
-            sDefaultContentInsetsApplier;
+    boolean mDecorFitsSystemWindows = true;
 
     static class WindowManagerHolder {
         static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface(
@@ -2138,9 +2137,7 @@
     /** Notify when decor view is attached to window and {@link ViewRootImpl} is available. */
     void onViewRootImplSet(ViewRootImpl viewRoot) {
         viewRoot.setActivityConfigCallback(mActivityConfigCallback);
-        viewRoot.setOnContentApplyWindowInsetsListener(
-                mPendingOnContentApplyWindowInsetsListener);
-        mPendingOnContentApplyWindowInsetsListener = null;
+        applyDecorFitsSystemWindows();
     }
 
     static private final String FOCUSED_ID_TAG = "android:focusedViewId";
@@ -3907,14 +3904,16 @@
 
     @Override
     public void setDecorFitsSystemWindows(boolean decorFitsSystemWindows) {
+        mDecorFitsSystemWindows = decorFitsSystemWindows;
+        applyDecorFitsSystemWindows();
+    }
+
+    private void applyDecorFitsSystemWindows() {
         ViewRootImpl impl = getViewRootImplOrNull();
-        OnContentApplyWindowInsetsListener listener = decorFitsSystemWindows
-                ? sDefaultContentInsetsApplier
-                : null;
         if (impl != null) {
-            impl.setOnContentApplyWindowInsetsListener(listener);
-        } else {
-            mPendingOnContentApplyWindowInsetsListener = listener;
+            impl.setOnContentApplyWindowInsetsListener(mDecorFitsSystemWindows
+                    ? sDefaultContentInsetsApplier
+                    : null);
         }
     }
 }
diff --git a/core/java/com/android/internal/util/FunctionalUtils.java b/core/java/com/android/internal/util/FunctionalUtils.java
index 3c97917..9168438 100644
--- a/core/java/com/android/internal/util/FunctionalUtils.java
+++ b/core/java/com/android/internal/util/FunctionalUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.util;
 
+import android.annotation.NonNull;
 import android.os.RemoteException;
 import android.util.ExceptionUtils;
 
@@ -218,4 +219,37 @@
             }
         }
     }
+
+    // TODO: add unit test
+    /**
+     * Gets a user-friendly name for a lambda function.
+     */
+    @NonNull
+    public static String getLambdaName(@NonNull Object function) {
+        // Full function has one of the following formats:
+        //   package-$$Lambda$class$randomId
+        //   package-$$Lambda$randomId
+        //
+        // We just want just package.class$Lambda (or package$Lambda) respectively
+
+        final String fullFunction = function.toString();
+
+        final int endPkgIdx = fullFunction.indexOf("-$$");
+        if (endPkgIdx == -1) return fullFunction;
+
+        // firstDollarIdx could be either beginning of class or beginning of the random id
+        final int firstDollarIdx = fullFunction.indexOf('$', endPkgIdx + 3);
+        if (firstDollarIdx == -1) return fullFunction;
+
+        final int endClassIdx = fullFunction.indexOf('$', firstDollarIdx + 1);
+        if (endClassIdx == -1) {
+            // Just package
+            return fullFunction.substring(0, endPkgIdx - 1) + "$Lambda";
+        }
+
+        // Package + class
+        return fullFunction.substring(0, endPkgIdx)
+                + fullFunction.substring(firstDollarIdx + 1, endClassIdx)
+                + "$Lambda";
+    }
 }
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
index e4a4408..1646a07 100755
--- a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.util.function.pooled;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Message;
 import android.text.TextUtils;
@@ -25,6 +24,7 @@
 
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.BitUtils;
+import com.android.internal.util.FunctionalUtils;
 import com.android.internal.util.function.DecConsumer;
 import com.android.internal.util.function.DecFunction;
 import com.android.internal.util.function.DecPredicate;
@@ -580,36 +580,6 @@
         return r;
     }
 
-    // TODO: add unit test
-    @NonNull
-    private static String getFriendlyName(@NonNull Object function) {
-        // Full function has one of the following formats:
-        //   package-$$Lambda$class$randomId
-        //   package-$$Lambda$randomId
-        //
-        // We just want just package.class$Lambda (or package$Lambda) respectively
-
-        final String fullFunction = function.toString();
-
-        final int endPkgIdx = fullFunction.indexOf("-$$");
-        if (endPkgIdx == -1) return fullFunction;
-
-        // firstDollarIdx could be either beginning of class or beginning of the random id
-        final int firstDollarIdx = fullFunction.indexOf('$', endPkgIdx + 3);
-        if (firstDollarIdx == -1) return fullFunction;
-
-        final int endClassIdx = fullFunction.indexOf('$', firstDollarIdx + 1);
-        if (endClassIdx == -1) {
-            // Just package
-            return fullFunction.substring(0, endPkgIdx - 1) + "$Lambda";
-        }
-
-        // Package + class
-        return fullFunction.substring(0, endPkgIdx)
-                + fullFunction.substring(firstDollarIdx + 1, endClassIdx)
-                + "$Lambda";
-    }
-
     private static void setIfInBounds(Object[] array, int i, Object a) {
         if (i < ArrayUtils.size(array)) array[i] = a;
     }
@@ -651,7 +621,7 @@
 
     @Override
     public String getTraceName() {
-        return getFriendlyName(mFunc);
+        return FunctionalUtils.getLambdaName(mFunc);
     }
 
     private boolean isRecycled() {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index b11c4c9..1cfa12d 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1430,6 +1430,12 @@
 
     client->setGlobalShadowSettings(ambientColor, spotColor, lightPosY, lightPosZ, lightRadius);
 }
+
+static jlong nativeGetHandle(JNIEnv* env, jclass clazz, jlong nativeObject) {
+    SurfaceControl *surfaceControl = reinterpret_cast<SurfaceControl*>(nativeObject);
+    return reinterpret_cast<jlong>(surfaceControl->getHandle().get());
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod sSurfaceControlMethods[] = {
@@ -1606,6 +1612,8 @@
             (void*)nativeMirrorSurface },
     {"nativeSetGlobalShadowSettings", "([F[FFFF)V",
             (void*)nativeSetGlobalShadowSettings },
+    {"nativeGetHandle", "(J)J",
+            (void*)nativeGetHandle },
 };
 
 int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index ea3c0fa..924dc4b 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -110,7 +110,6 @@
 using android::base::StringPrintf;
 using android::base::WriteStringToFile;
 using android::base::GetBoolProperty;
-using android::base::GetProperty;
 
 #define CREATE_ERROR(...) StringPrintf("%s:%d: ", __FILE__, __LINE__). \
                               append(StringPrintf(__VA_ARGS__))
@@ -170,18 +169,6 @@
 
 static constexpr int DEFAULT_DATA_DIR_PERMISSION = 0751;
 
-/**
- * Property to control if app data isolation is enabled.
- */
-static const std::string ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY =
-    "persist.zygote.app_data_isolation";
-
-/**
- * Property to enable app data isolation for sdcard obb or data in vold.
- */
-static const std::string ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY =
-    "persist.sys.vold_app_data_isolation_enabled";
-
 static constexpr const uint64_t UPPER_HALF_WORD_MASK = 0xFFFF'FFFF'0000'0000;
 static constexpr const uint64_t LOWER_HALF_WORD_MASK = 0x0000'0000'FFFF'FFFF;
 
@@ -1319,20 +1306,13 @@
  * be decrypted after storage is decrypted.
  *
  */
-static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list,
-    uid_t uid, const char* process_name, jstring managed_nice_name,
-    fail_fn_t fail_fn) {
+static void isolateAppData(JNIEnv* env, const std::vector<std::string>& merged_data_info_list,
+    uid_t uid, const char* process_name,
+    jstring managed_nice_name, fail_fn_t fail_fn) {
 
   const userid_t userId = multiuser_get_user_id(uid);
 
-  auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
-
-  int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0;
-  // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode>
-  if ((size % 3) != 0) {
-    fail_fn(CREATE_ERROR("Wrong pkg_inode_list size %d", size));
-  }
-  ensureInAppMountNamespace(fail_fn);
+  int size = merged_data_info_list.size();
 
   // Mount tmpfs on all possible data directories, so app no longer see the original apps data.
   char internalCePath[PATH_MAX];
@@ -1377,14 +1357,10 @@
   bool legacySymlinkCreated = false;
 
   for (int i = 0; i < size; i += 3) {
-    jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i));
-    std::string packageName = extract_fn(package_str).value();
+    std::string const & packageName = merged_data_info_list[i];
+    std::string const & volUuid  = merged_data_info_list[i + 1];
+    std::string const & inode = merged_data_info_list[i + 2];
 
-    jstring vol_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 1));
-    std::string volUuid = extract_fn(vol_str).value();
-
-    jstring inode_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 2));
-    std::string inode = extract_fn(inode_str).value();
     std::string::size_type sz;
     long long ceDataInode = std::stoll(inode, &sz);
 
@@ -1482,6 +1458,48 @@
   freecon(dataDataContext);
 }
 
+static void insertPackagesToMergedList(JNIEnv* env,
+  std::vector<std::string>& merged_data_info_list,
+  jobjectArray data_info_list, const char* process_name,
+  jstring managed_nice_name, fail_fn_t fail_fn) {
+
+  auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
+
+  int size = (data_info_list != nullptr) ? env->GetArrayLength(data_info_list) : 0;
+  // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode>
+  if ((size % 3) != 0) {
+    fail_fn(CREATE_ERROR("Wrong data_info_list size %d", size));
+  }
+
+  for (int i = 0; i < size; i += 3) {
+    jstring package_str = (jstring) (env->GetObjectArrayElement(data_info_list, i));
+    std::string packageName = extract_fn(package_str).value();
+    merged_data_info_list.push_back(packageName);
+
+    jstring vol_str = (jstring) (env->GetObjectArrayElement(data_info_list, i + 1));
+    std::string volUuid = extract_fn(vol_str).value();
+    merged_data_info_list.push_back(volUuid);
+
+    jstring inode_str = (jstring) (env->GetObjectArrayElement(data_info_list, i + 2));
+    std::string inode = extract_fn(inode_str).value();
+    merged_data_info_list.push_back(inode);
+  }
+}
+
+static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list,
+    jobjectArray whitelisted_data_info_list, uid_t uid, const char* process_name,
+    jstring managed_nice_name, fail_fn_t fail_fn) {
+
+  ensureInAppMountNamespace(fail_fn);
+  std::vector<std::string> merged_data_info_list;
+  insertPackagesToMergedList(env, merged_data_info_list, pkg_data_info_list,
+          process_name, managed_nice_name, fail_fn);
+  insertPackagesToMergedList(env, merged_data_info_list, whitelisted_data_info_list,
+          process_name, managed_nice_name, fail_fn);
+
+  isolateAppData(env, merged_data_info_list, uid, process_name, managed_nice_name, fail_fn);
+}
+
 /**
  * Like isolateAppData(), isolate jit profile directories, so apps don't see what
  * other apps are installed by reading content inside /data/misc/profiles/cur.
@@ -1594,7 +1612,9 @@
                              jstring managed_nice_name, bool is_system_server,
                              bool is_child_zygote, jstring managed_instruction_set,
                              jstring managed_app_data_dir, bool is_top_app,
-                             jobjectArray pkg_data_info_list, bool mount_storage_dirs) {
+                             jobjectArray pkg_data_info_list,
+                             jobjectArray whitelisted_data_info_list,
+                             bool mount_data_dirs, bool mount_storage_dirs) {
   const char* process_name = is_system_server ? "system_server" : "zygote";
   auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1);
   auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
@@ -1628,12 +1648,14 @@
   // give a null in same_uid_pkgs and private_volumes so they don't need app data isolation.
   // Isolated process / webview / app zygote should be gated by SELinux and file permission
   // so they can't even traverse CE / DE directories.
-  if (pkg_data_info_list != nullptr
-      && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) {
-    isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
+  if (mount_data_dirs) {
+    isolateAppData(env, pkg_data_info_list, whitelisted_data_info_list,
+            uid, process_name, managed_nice_name, fail_fn);
     isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
   }
-  if ((mount_external != MOUNT_EXTERNAL_INSTALLER) && mount_storage_dirs) {
+  if (mount_external != MOUNT_EXTERNAL_INSTALLER &&
+      mount_external != MOUNT_EXTERNAL_PASS_THROUGH &&
+      mount_storage_dirs) {
     BindMountStorageDirs(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
   }
 
@@ -2003,7 +2025,8 @@
         jint mount_external, jstring se_info, jstring nice_name,
         jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote,
         jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
-        jobjectArray pkg_data_info_list, jboolean mount_storage_dirs) {
+        jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list,
+        jboolean mount_data_dirs, jboolean mount_storage_dirs) {
     jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
 
     if (UNLIKELY(managed_fds_to_close == nullptr)) {
@@ -2041,6 +2064,8 @@
                        mount_external, se_info, nice_name, false,
                        is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
                        is_top_app == JNI_TRUE, pkg_data_info_list,
+                       whitelisted_data_info_list,
+                       mount_data_dirs == JNI_TRUE,
                        mount_storage_dirs == JNI_TRUE);
     }
     return pid;
@@ -2076,7 +2101,8 @@
                        permitted_capabilities, effective_capabilities,
                        MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true,
                        false, nullptr, nullptr, /* is_top_app= */ false,
-                       /* pkg_data_info_list */ nullptr, false);
+                       /* pkg_data_info_list */ nullptr,
+                       /* whitelisted_data_info_list */ nullptr, false, false);
   } else if (pid > 0) {
       // The zygote process checks whether the child process has died or not.
       ALOGI("System server process %d has been created", pid);
@@ -2206,15 +2232,16 @@
     jint runtime_flags, jobjectArray rlimits,
     jint mount_external, jstring se_info, jstring nice_name,
     jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app,
-    jobjectArray pkg_data_info_list, jboolean mount_storage_dirs) {
+    jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list,
+    jboolean mount_data_dirs, jboolean mount_storage_dirs) {
   jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote);
 
   SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits,
                    capabilities, capabilities,
                    mount_external, se_info, nice_name, false,
                    is_child_zygote == JNI_TRUE, instruction_set, app_data_dir,
-                   is_top_app == JNI_TRUE, pkg_data_info_list,
-                   mount_storage_dirs == JNI_TRUE);
+                   is_top_app == JNI_TRUE, pkg_data_info_list, whitelisted_data_info_list,
+                   mount_data_dirs == JNI_TRUE, mount_storage_dirs == JNI_TRUE);
 }
 
 /**
@@ -2405,10 +2432,19 @@
     return -1;
 }
 
+static jboolean com_android_internal_os_Zygote_nativeSupportsTaggedPointers(JNIEnv* env, jclass) {
+#ifdef __aarch64__
+  int res = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
+  return res >= 0 && res & PR_TAGGED_ADDR_ENABLE;
+#else
+  return false;
+#endif
+}
+
 static const JNINativeMethod gMethods[] = {
         {"nativeForkAndSpecialize",
          "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/"
-         "String;Z[Ljava/lang/String;Z)I",
+         "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I",
          (void*)com_android_internal_os_Zygote_nativeForkAndSpecialize},
         {"nativeForkSystemServer", "(II[II[[IJJ)I",
          (void*)com_android_internal_os_Zygote_nativeForkSystemServer},
@@ -2421,7 +2457,7 @@
         {"nativeForkUsap", "(II[IZ)I", (void*)com_android_internal_os_Zygote_nativeForkUsap},
         {"nativeSpecializeAppProcess",
          "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/"
-         "String;Z[Ljava/lang/String;Z)V",
+         "String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V",
          (void*)com_android_internal_os_Zygote_nativeSpecializeAppProcess},
         {"nativeInitNativeState", "(Z)V",
          (void*)com_android_internal_os_Zygote_nativeInitNativeState},
@@ -2440,6 +2476,8 @@
          (void*)com_android_internal_os_Zygote_nativeBoostUsapPriority},
         {"nativeParseSigChld", "([BI[I)I",
          (void*)com_android_internal_os_Zygote_nativeParseSigChld},
+        {"nativeSupportsTaggedPointers", "()Z",
+         (void*)com_android_internal_os_Zygote_nativeSupportsTaggedPointers},
 };
 
 int register_com_android_internal_os_Zygote(JNIEnv* env) {
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 6321651..3e007e4 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2674,4 +2674,14 @@
     // CATEGORY: SETTINGS
     // OS: R
     FUELGAUGE_ADVANCED_BATTERY_OPTION = 1842;
+
+    // OPEN: Settings > System > Gestures > Power menu
+    // CATEGORY: SETTINGS
+    // OS: R
+    POWER_MENU_SETTINGS = 1843;
+
+    // OPEN: Settings > System > Gestures > Power menu > Device controls
+    // CATEGORY: SETTINGS
+    // OS: R
+    DEVICE_CONTROLS_SETTINGS = 1844;
 }
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 684a292..896ee4f 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -186,4 +186,15 @@
   RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK= 159;
   RESOLVER_EMPTY_STATE_NO_APPS_RESOLVED= 160;
   RESOLVER_AUTOLAUNCH_CROSS_PROFILE_TARGET = 161;
+  CROSS_PROFILE_SETTINGS_PAGE_LAUNCHED_FROM_APP = 162;
+  CROSS_PROFILE_SETTINGS_PAGE_LAUNCHED_FROM_SETTINGS = 163;
+  CROSS_PROFILE_SETTINGS_PAGE_ADMIN_RESTRICTED = 164;
+  CROSS_PROFILE_SETTINGS_PAGE_MISSING_WORK_APP = 165;
+  CROSS_PROFILE_SETTINGS_PAGE_MISSING_PERSONAL_APP = 166;
+  CROSS_PROFILE_SETTINGS_PAGE_MISSING_INSTALL_BANNER_INTENT = 167;
+  CROSS_PROFILE_SETTINGS_PAGE_INSTALL_BANNER_CLICKED = 168;
+  CROSS_PROFILE_SETTINGS_PAGE_INSTALL_BANNER_NO_INTENT_CLICKED  = 169;
+  CROSS_PROFILE_SETTINGS_PAGE_USER_CONSENTED = 170;
+  CROSS_PROFILE_SETTINGS_PAGE_USER_DECLINED_CONSENT = 171;
+  CROSS_PROFILE_SETTINGS_PAGE_PERMISSION_REVOKED = 172;
 }
diff --git a/core/proto/android/stats/dnsresolver/dns_resolver.proto b/core/proto/android/stats/dnsresolver/dns_resolver.proto
index 76f8f0f..61b9b25 100644
--- a/core/proto/android/stats/dnsresolver/dns_resolver.proto
+++ b/core/proto/android/stats/dnsresolver/dns_resolver.proto
@@ -62,6 +62,13 @@
     NS_R_NOTAUTH = 9;   // Not authoritative for zone
     NS_R_NOTZONE = 10;  // Zone of record different from zone section
     NS_R_MAX = 11;
+    // Define rcode=12~15(UNASSIGNED) in rcode enum type.
+    // Some DNS Servers might return undefined code to devices.
+    // Without the enum definition, that would be noise for our dashboard.
+    NS_R_UNASSIGNED12 = 12; // Unassigned
+    NS_R_UNASSIGNED13 = 13; // Unassigned
+    NS_R_UNASSIGNED14 = 14; // Unassigned
+    NS_R_UNASSIGNED15 = 15; // Unassigned
     // The following are EDNS extended rcodes
     NS_R_BADVERS = 16;
     // The following are TSIG errors
@@ -170,12 +177,22 @@
     NT_BLUETOOTH = 3;
     // Indicates this network uses an Ethernet transport.
     NT_ETHERNET = 4;
-    // Indicates this network uses a VPN transport.
-    NT_VPN = 5;
+    // Indicates this network uses a VPN transport, now deprecated.
+    NT_VPN = 5 [deprecated=true];
     // Indicates this network uses a Wi-Fi Aware transport.
     NT_WIFI_AWARE = 6;
     // Indicates this network uses a LoWPAN transport.
     NT_LOWPAN = 7;
+    // Indicates this network uses a Cellular+VPN transport.
+    NT_CELLULAR_VPN = 8;
+    // Indicates this network uses a Wi-Fi+VPN transport.
+    NT_WIFI_VPN = 9;
+    // Indicates this network uses a Bluetooth+VPN transport.
+    NT_BLUETOOTH_VPN = 10;
+    // Indicates this network uses an Ethernet+VPN transport.
+    NT_ETHERNET_VPN = 11;
+    // Indicates this network uses a Wi-Fi+Cellular+VPN transport.
+    NT_WIFI_CELLULAR_VPN = 12;
 }
 
 enum CacheStatus{
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 451363f..1c978bf 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -351,8 +351,9 @@
     <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP" />
     <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP" />
     <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED" />
-    <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_CARRIER" />
-    <protected-broadcast android:name="com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_CARRIER" />
+    <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_ALLOWED_CARRIER" />
+    <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_DISALLOWED_CARRIER" />
+    <protected-broadcast android:name="com.android.server.wifi.action.CarrierNetwork.USER_DISMISSED" />
     <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION" />
     <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK" />
     <protected-broadcast android:name="com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK" />
@@ -4122,7 +4123,10 @@
          set of pages referenced over time.
          <p>Declaring the permission implies intention to use the API and the user of the
          device can grant permission through the Settings application.
-         <p>Protection level: signature|privileged|appop -->
+         <p>Protection level: signature|privileged|appop
+         <p>A data loader has to be the one which provides data to install an app.
+         <p>A data loader has to have both permission:LOADER_USAGE_STATS AND
+         appop:LOADER_USAGE_STATS allowed to be able to access the read logs. -->
     <permission android:name="android.permission.LOADER_USAGE_STATS"
         android:protectionLevel="signature|privileged|appop" />
     <uses-permission android:name="android.permission.LOADER_USAGE_STATS" />
@@ -5454,6 +5458,13 @@
             </intent-filter>
         </service>
 
+        <provider
+            android:name="com.android.server.textclassifier.IconsContentProvider"
+            android:authorities="com.android.textclassifier.icons"
+            android:enabled="true"
+            android:exported="true">
+        </provider>
+
     </application>
 
 </manifest>
diff --git a/core/tests/coretests/src/android/app/WindowContextTest.java b/core/tests/coretests/src/android/app/WindowContextTest.java
new file mode 100644
index 0000000..630e16a
--- /dev/null
+++ b/core/tests/coretests/src/android/app/WindowContextTest.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 android.app;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import android.view.IWindowManager;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link WindowContext}
+ *
+ * <p>Build/Install/Run:
+ *  atest FrameworksCoreTests:WindowContextTest
+ *
+ * <p>This test class is a part of Window Manager Service tests and specified in
+ * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
+ */
+@FlakyTest(bugId = 150812449, detail = "Remove after confirmed it's stable.")
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class WindowContextTest {
+    @Test
+    public void testWindowContextRelease_doRemoveWindowToken() throws Throwable {
+        final Context instContext = InstrumentationRegistry.getInstrumentation()
+                .getTargetContext();
+        final Display display = instContext.getSystemService(DisplayManager.class)
+                .getDisplay(DEFAULT_DISPLAY);
+        final Context context = instContext.createDisplayContext(display);
+        final WindowContext windowContext = new WindowContext(context, TYPE_APPLICATION_OVERLAY,
+                null /* options */);
+
+        final IBinder token = windowContext.getActivityToken();
+
+        final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
+        assertTrue("Token must be registered to WMS", wms.isWindowToken(token));
+
+        windowContext.release();
+
+        assertFalse("Token must be unregistered to WMS", wms.isWindowToken(token));
+    }
+}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index cbb379b..d432dda 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -696,8 +696,7 @@
 
         // Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will
         // attempt to release mLeash directly.
-        SurfaceControl copy = new SurfaceControl();
-        copy.copyFrom(mLeash);
+        SurfaceControl copy = new SurfaceControl(mLeash);
         return new InsetsSourceControl(type, copy, new Point());
     }
 
diff --git a/core/tests/coretests/src/android/view/WindowMetricsTest.java b/core/tests/coretests/src/android/view/WindowMetricsTest.java
index 74524bf..ddc977d 100644
--- a/core/tests/coretests/src/android/view/WindowMetricsTest.java
+++ b/core/tests/coretests/src/android/view/WindowMetricsTest.java
@@ -56,17 +56,17 @@
 
     @Before
     public void setUp() {
-        final Context insetContext = InstrumentationRegistry.getInstrumentation()
+        final Context instContext = InstrumentationRegistry.getInstrumentation()
                 .getTargetContext();
-        final Display display = insetContext.getSystemService(DisplayManager.class)
+        final Display display = instContext.getSystemService(DisplayManager.class)
                 .getDisplay(DEFAULT_DISPLAY);
-        mWindowContext = insetContext.createDisplayContext(display)
+        mWindowContext = instContext.createDisplayContext(display)
                 .createWindowContext(TYPE_APPLICATION_OVERLAY, null /* options */);
         mWm = mWindowContext.getSystemService(WindowManager.class);
     }
 
     @Test
-    public void testAddViewANdRemoveView_GetMetrics_DoNotCrash() {
+    public void testAddViewAndRemoveView_GetMetrics_DoNotCrash() {
         final View view = new View(mWindowContext);
         final WindowManager.LayoutParams params =
                 new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
diff --git a/core/tests/overlaytests/host/Android.bp b/core/tests/overlaytests/host/Android.bp
index a2fcef5..2b38cca 100644
--- a/core/tests/overlaytests/host/Android.bp
+++ b/core/tests/overlaytests/host/Android.bp
@@ -16,7 +16,7 @@
     name: "OverlayHostTests",
     srcs: ["src/**/*.java"],
     libs: ["tradefed"],
-    test_suites: ["general-tests"],
+    test_suites: ["device-tests"],
     target_required: [
         "OverlayHostTests_NonPlatformSignatureOverlay",
         "OverlayHostTests_PlatformSignatureStaticOverlay",
diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
index eec7be2..d898d22 100644
--- a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
+++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
@@ -78,14 +78,9 @@
     }
 
     @Test
-    public void failToInstallPlatformSignedStaticOverlay() throws Exception {
-        try {
-            installPackage("OverlayHostTests_PlatformSignatureStaticOverlay.apk");
-            fail("installed a static overlay");
-        } catch (Exception e) {
-            // Expected.
-        }
-        assertFalse(overlayManagerContainsPackage(SIG_OVERLAY_PACKAGE_NAME));
+    public void installedIsStaticOverlayIsMutable() throws Exception {
+        installPackage("OverlayHostTests_PlatformSignatureStaticOverlay.apk");
+        assertTrue(isOverlayMutable(SIG_OVERLAY_PACKAGE_NAME));
     }
 
     @Test
@@ -229,6 +224,10 @@
         return shell("cmd overlay list").contains(pkg);
     }
 
+    private boolean isOverlayMutable(String pkg) throws Exception {
+        return shell("cmd overlay dump ismutable " + pkg).contains("true");
+    }
+
     private String shell(final String cmd) throws Exception {
         return getDevice().executeShellCommand(cmd);
     }
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
index cc7704b..f3c0abd 100644
--- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
@@ -20,7 +20,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayHostTests_NonPlatformSignatureOverlay
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad
 include $(BUILD_PACKAGE)
 
@@ -28,7 +28,8 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureStaticOverlay
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
+LOCAL_CERTIFICATE := platform
 LOCAL_MANIFEST_FILE := static/AndroidManifest.xml
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static
 include $(BUILD_PACKAGE)
@@ -37,7 +38,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlay
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_CERTIFICATE := platform
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
 LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
index f8607f4..878f05d 100644
--- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
@@ -19,7 +19,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
 LOCAL_PACKAGE_NAME := OverlayHostTests_UpdateOverlay
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
 LOCAL_USE_AAPT2 := true
 LOCAL_AAPT_FLAGS := --no-resource-removal
@@ -31,7 +31,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV1
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_CERTIFICATE := platform
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
 LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
@@ -43,7 +43,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayHostTests_FrameworkOverlayV2
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_CERTIFICATE := platform
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
 LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
@@ -57,7 +57,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
 LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v1/res
@@ -68,7 +68,7 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2
 LOCAL_SDK_VERSION := current
-LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_COMPATIBILITY_SUITE := device-tests
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
 LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/app/v2/res
diff --git a/core/tests/overlaytests/remount/host/Android.bp b/core/tests/overlaytests/remount/Android.bp
similarity index 91%
rename from core/tests/overlaytests/remount/host/Android.bp
rename to core/tests/overlaytests/remount/Android.bp
index 3825c55..9baedf8 100644
--- a/core/tests/overlaytests/remount/host/Android.bp
+++ b/core/tests/overlaytests/remount/Android.bp
@@ -24,5 +24,7 @@
         ":OverlayRemountedTest_SharedLibrary",
         ":OverlayRemountedTest_SharedLibraryOverlay",
         ":OverlayRemountedTest_Target",
+        ":OverlayRemountedTest_TargetUpgrade",
+        ":OverlayRemountedTest_Overlay",
     ],
 }
diff --git a/core/tests/overlaytests/remount/host/AndroidTest.xml b/core/tests/overlaytests/remount/AndroidTest.xml
similarity index 100%
rename from core/tests/overlaytests/remount/host/AndroidTest.xml
rename to core/tests/overlaytests/remount/AndroidTest.xml
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
deleted file mode 100644
index 06b2ac8..0000000
--- a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.overlaytest.remounted;
-
-import static org.junit.Assert.assertTrue;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.RuleChain;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class OverlaySharedLibraryTest extends BaseHostJUnit4Test {
-    private static final String TARGET_APK = "OverlayRemountedTest_Target.apk";
-    private static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target";
-    private static final String SHARED_LIBRARY_APK =
-            "OverlayRemountedTest_SharedLibrary.apk";
-    private static final String SHARED_LIBRARY_PACKAGE =
-            "com.android.overlaytest.remounted.shared_library";
-    private static final String SHARED_LIBRARY_OVERLAY_APK =
-            "OverlayRemountedTest_SharedLibraryOverlay.apk";
-    private static final String SHARED_LIBRARY_OVERLAY_PACKAGE =
-            "com.android.overlaytest.remounted.shared_library.overlay";
-
-    public final TemporaryFolder temporaryFolder = new TemporaryFolder();
-    public final SystemPreparer preparer = new SystemPreparer(temporaryFolder, this::getDevice);
-
-    @Rule
-    public final RuleChain ruleChain = RuleChain.outerRule(temporaryFolder).around(preparer);
-
-    @Before
-    public void startBefore() throws DeviceNotAvailableException {
-        getDevice().waitForDeviceAvailable();
-    }
-
-    @Test
-    public void testSharedLibrary() throws Exception {
-        final String targetResource = resourceName(TARGET_PACKAGE, "bool",
-                "uses_shared_library_overlaid");
-        final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
-                "shared_library_overlaid");
-
-        preparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
-                .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
-                .reboot()
-                .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false)
-                .installResourceApk(TARGET_APK, TARGET_PACKAGE);
-
-        // The shared library resource is not currently overlaid.
-        assertResource(targetResource, "false");
-        assertResource(libraryResource, "false");
-
-        // Overlay the shared library resource.
-        preparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true);
-        assertResource(targetResource, "true");
-        assertResource(libraryResource, "true");
-    }
-
-    @Test
-    public void testSharedLibraryPreEnabled() throws Exception {
-        final String targetResource = resourceName(TARGET_PACKAGE, "bool",
-                "uses_shared_library_overlaid");
-        final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
-                "shared_library_overlaid");
-
-        preparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
-                .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
-                .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true)
-                .reboot()
-                .installResourceApk(TARGET_APK, TARGET_PACKAGE);
-
-        assertResource(targetResource, "true");
-        assertResource(libraryResource, "true");
-    }
-
-    /** Builds the full name of a resource in the form package:type/entry. */
-    String resourceName(String pkg, String type, String entry) {
-        return String.format("%s:%s/%s", pkg, type, entry);
-    }
-
-    void assertResource(String resourceName, String expectedValue)
-            throws DeviceNotAvailableException {
-        final String result = getDevice().executeShellCommand(
-                String.format("cmd overlay lookup %s %s", TARGET_PACKAGE, resourceName));
-        assertTrue(String.format("expected: <[%s]> in: <[%s]>", expectedValue, result),
-                result.equals(expectedValue + "\n") ||
-                result.endsWith("-> " + expectedValue + "\n"));
-    }
-}
diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java
new file mode 100644
index 0000000..00a53c4
--- /dev/null
+++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlayRemountedTestBase.java
@@ -0,0 +1,84 @@
+/*
+ * 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.overlaytest.remounted;
+
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+
+public class OverlayRemountedTestBase extends BaseHostJUnit4Test {
+    static final long ASSERT_RESOURCE_TIMEOUT_MS = 30000;
+
+    static final String TARGET_APK = "OverlayRemountedTest_Target.apk";
+    static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target";
+    static final String TARGET_UPGRADE_APK = "OverlayRemountedTest_TargetUpgrade.apk";
+    static final String OVERLAY_APK = "OverlayRemountedTest_Overlay.apk";
+    static final String OVERLAY_PACKAGE = "com.android.overlaytest.remounted.target.overlay";
+    static final String SHARED_LIBRARY_APK =
+            "OverlayRemountedTest_SharedLibrary.apk";
+    static final String SHARED_LIBRARY_PACKAGE =
+            "com.android.overlaytest.remounted.shared_library";
+    static final String SHARED_LIBRARY_OVERLAY_APK =
+            "OverlayRemountedTest_SharedLibraryOverlay.apk";
+    static final String SHARED_LIBRARY_OVERLAY_PACKAGE =
+            "com.android.overlaytest.remounted.shared_library.overlay";
+
+    private final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+    protected final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder,
+            this::getDevice);
+
+    @Rule
+    public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer);
+
+    @Before
+    public void startBefore() throws DeviceNotAvailableException {
+        getDevice().waitForDeviceAvailable();
+    }
+
+    /** Builds the full name of a resource in the form package:type/entry. */
+    String resourceName(String pkg, String type, String entry) {
+        return String.format("%s:%s/%s", pkg, type, entry);
+    }
+
+    void assertResource(String resourceName, String expectedValue)
+            throws DeviceNotAvailableException {
+        String result = null;
+
+        final long endMillis = System.currentTimeMillis() + ASSERT_RESOURCE_TIMEOUT_MS;
+        while (System.currentTimeMillis() <= endMillis) {
+            result = getDevice().executeShellCommand(
+                    String.format("cmd overlay lookup %s %s", TARGET_PACKAGE, resourceName));
+            if (result.equals(expectedValue + "\n") ||
+                    result.endsWith("-> " + expectedValue + "\n")) {
+                return;
+            }
+
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException ignore) {
+            }
+        }
+
+        fail(String.format("expected: <[%s]> in: <[%s]>", expectedValue, result));
+    }
+}
diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
new file mode 100644
index 0000000..49d1894
--- /dev/null
+++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest.remounted;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class OverlaySharedLibraryTest extends OverlayRemountedTestBase {
+
+    @Test
+    public void testSharedLibrary() throws Exception {
+        final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+                "uses_shared_library_overlaid");
+        final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
+                "shared_library_overlaid");
+
+        mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+                .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
+                .reboot()
+                .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false)
+                .installResourceApk(TARGET_APK, TARGET_PACKAGE);
+
+        // The shared library resource is not currently overlaid.
+        assertResource(targetResource, "false");
+        assertResource(libraryResource, "false");
+
+        // Overlay the shared library resource.
+        mPreparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true);
+        assertResource(targetResource, "true");
+        assertResource(libraryResource, "true");
+    }
+
+    @Test
+    public void testSharedLibraryPreEnabled() throws Exception {
+        final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+                "uses_shared_library_overlaid");
+        final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
+                "shared_library_overlaid");
+
+        mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+                .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
+                .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true)
+                .reboot()
+                .installResourceApk(TARGET_APK, TARGET_PACKAGE);
+
+        assertResource(targetResource, "true");
+        assertResource(libraryResource, "true");
+    }
+}
diff --git a/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java
new file mode 100644
index 0000000..f685ec1
--- /dev/null
+++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/PackagedUpgradedTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.overlaytest.remounted;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PackagedUpgradedTest extends OverlayRemountedTestBase {
+
+    @Test
+    public void testTargetUpgrade() throws Exception {
+        final String targetOverlaid = resourceName(TARGET_PACKAGE, "bool", "target_overlaid");
+        final String targetReference = resourceName(TARGET_PACKAGE, "bool", "target_reference");
+
+        mPreparer.pushResourceFile(TARGET_APK, "/product/app/OverlayTarget.apk")
+                .reboot()
+                .installResourceApk(OVERLAY_APK, OVERLAY_PACKAGE)
+                .setOverlayEnabled(OVERLAY_PACKAGE, true);
+
+        assertResource(targetReference, "@" + 0x7f010000 + " -> true");
+        assertResource(targetOverlaid, "true");
+
+        mPreparer.installResourceApk(TARGET_UPGRADE_APK, TARGET_PACKAGE);
+
+        assertResource(targetReference, "@" + 0x7f0100ff + " -> true");
+        assertResource(targetOverlaid, "true");
+    }
+
+    @Test
+    public void testTargetRelocated() throws Exception {
+        final String targetOverlaid = resourceName(TARGET_PACKAGE, "bool", "target_overlaid");
+        final String originalPath = "/product/app/OverlayTarget.apk";
+
+        mPreparer.pushResourceFile(TARGET_APK, originalPath)
+                .reboot()
+                .installResourceApk(OVERLAY_APK, OVERLAY_PACKAGE)
+                .setOverlayEnabled(OVERLAY_PACKAGE, true);
+
+        assertResource(targetOverlaid, "true");
+
+        mPreparer.remount();
+        getDevice().deleteFile(originalPath);
+        mPreparer.pushResourceFile(TARGET_UPGRADE_APK, "/product/app/OverlayTarget2.apk")
+                .reboot();
+
+        assertResource(targetOverlaid, "true");
+    }
+}
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java
similarity index 76%
rename from core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
rename to core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java
index 8696091..b45b8ea 100644
--- a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
+++ b/core/tests/overlaytests/remount/src/com/android/overlaytest/remounted/SystemPreparer.java
@@ -18,8 +18,6 @@
 
 import static org.junit.Assert.assertTrue;
 
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 
@@ -32,10 +30,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-import java.util.concurrent.FutureTask;
-import java.util.concurrent.TimeoutException;
 
 class SystemPreparer extends ExternalResource {
     private static final long OVERLAY_ENABLE_TIMEOUT_MS = 30000;
@@ -58,7 +52,7 @@
     SystemPreparer pushResourceFile(String resourcePath,
             String outputPath) throws DeviceNotAvailableException, IOException {
         final ITestDevice device = mDeviceProvider.getDevice();
-        device.executeAdbCommand("remount");
+        remount();
         assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath));
         mPushedFiles.add(outputPath);
         return this;
@@ -69,7 +63,7 @@
             throws DeviceNotAvailableException, IOException {
         final ITestDevice device = mDeviceProvider.getDevice();
         final File tmpFile = copyResourceToTemp(resourcePath);
-        final String result = device.installPackage(tmpFile, true);
+        final String result = device.installPackage(tmpFile, true /* reinstall */);
         Assert.assertNull(result);
         mInstalledPackages.add(packageName);
         return this;
@@ -77,34 +71,29 @@
 
     /** Sets the enable state of an overlay pacakage. */
     SystemPreparer setOverlayEnabled(String packageName, boolean enabled)
-            throws ExecutionException, DeviceNotAvailableException {
+            throws DeviceNotAvailableException {
         final ITestDevice device = mDeviceProvider.getDevice();
+        final String enable = enabled ? "enable" : "disable";
 
         // Wait for the overlay to change its enabled state.
-        final FutureTask<Boolean> enabledListener = new FutureTask<>(() -> {
-            while (true) {
-                device.executeShellCommand(String.format("cmd overlay %s %s",
-                        enabled ? "enable" : "disable", packageName));
-
-                final String result = device.executeShellCommand("cmd overlay dump " + packageName);
-                final int startIndex = result.indexOf("mIsEnabled");
-                final int endIndex = result.indexOf('\n', startIndex);
-                if (result.substring(startIndex, endIndex).contains((enabled) ? "true" : "false")) {
-                    return true;
-                }
+        final long endMillis = System.currentTimeMillis() + OVERLAY_ENABLE_TIMEOUT_MS;
+        String result;
+        while (System.currentTimeMillis() <= endMillis) {
+            device.executeShellCommand(String.format("cmd overlay %s %s", enable, packageName));
+            result = device.executeShellCommand("cmd overlay dump isenabled "
+                    + packageName);
+            if (((enabled) ? "true\n" : "false\n").equals(result)) {
+                return this;
             }
-        });
 
-        final Executor executor = (cmd) -> new Thread(cmd).start();
-        executor.execute(enabledListener);
-        try {
-            enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS);
-        } catch (InterruptedException ignored) {
-        } catch (TimeoutException e) {
-            throw new IllegalStateException(device.executeShellCommand("cmd overlay list"));
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException ignore) {
+            }
         }
 
-        return this;
+        throw new IllegalStateException(String.format("Failed to %s overlay %s:\n%s", enable,
+                packageName, device.executeShellCommand("cmd overlay list")));
     }
 
     /** Restarts the device and waits until after boot is completed. */
@@ -114,6 +103,11 @@
         return this;
     }
 
+    SystemPreparer remount() throws DeviceNotAvailableException {
+        mDeviceProvider.getDevice().executeAdbCommand("remount");
+        return this;
+    }
+
     /** Copies a file within the host test jar to a temporary file on the host machine. */
     private File copyResourceToTemp(String resourcePath) throws IOException {
         final File tempFile = mHostTempFolder.newFile(resourcePath);
@@ -138,7 +132,7 @@
     protected void after() {
         final ITestDevice device = mDeviceProvider.getDevice();
         try {
-            device.executeAdbCommand("remount");
+            remount();
             for (final String file : mPushedFiles) {
                 device.deleteFile(file);
             }
diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/test-apps/Overlay/Android.bp
similarity index 73%
copy from core/tests/overlaytests/remount/target/Android.bp
copy to core/tests/overlaytests/remount/test-apps/Overlay/Android.bp
index 83f9f28..4476019 100644
--- a/core/tests/overlaytests/remount/target/Android.bp
+++ b/core/tests/overlaytests/remount/test-apps/Overlay/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 The Android Open Source Project
+// 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.
@@ -13,8 +13,6 @@
 // limitations under the License.
 
 android_test_helper_app {
-    name: "OverlayRemountedTest_Target",
-    srcs: ["src/**/*.java"],
-    sdk_version: "test_current",
-    libs: ["OverlayRemountedTest_SharedLibrary"],
+    name: "OverlayRemountedTest_Overlay",
+    sdk_version: "current",
 }
diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml
similarity index 66%
copy from core/tests/overlaytests/remount/target/AndroidManifest.xml
copy to core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml
index dc07dca..d6d706c 100644
--- a/core/tests/overlaytests/remount/target/AndroidManifest.xml
+++ b/core/tests/overlaytests/remount/test-apps/Overlay/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -16,11 +16,8 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.overlaytest.remounted.target">
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-        <uses-library android:name="com.android.overlaytest.remounted.shared_library"
-                      android:required="true" />
-    </application>
-</manifest>
+    package="com.android.overlaytest.remounted.target.overlay">
+    <application android:hasCode="false" />
+    <overlay android:targetPackage="com.android.overlaytest.remounted.target"
+             android:targetName="TestResources" />
+</manifest>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml
similarity index 70%
copy from core/tests/overlaytests/remount/target/res/values/values.xml
copy to core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml
index b5f444a..675e44f 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Overlay/res/values/values.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,6 +15,6 @@
   ~ limitations under the License.
   -->
 
-<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
-    <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
+<resources>
+    <bool name="target_overlaid">true</bool>
 </resources>
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp b/core/tests/overlaytests/remount/test-apps/SharedLibrary/Android.bp
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
rename to core/tests/overlaytests/remount/test-apps/SharedLibrary/Android.bp
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/AndroidManifest.xml
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
rename to core/tests/overlaytests/remount/test-apps/SharedLibrary/AndroidManifest.xml
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/overlayable.xml
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
rename to core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/overlayable.xml
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/public.xml
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
rename to core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/public.xml
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/values.xml
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
rename to core/tests/overlaytests/remount/test-apps/SharedLibrary/res/values/values.xml
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/Android.bp
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
rename to core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/Android.bp
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/AndroidManifest.xml
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
rename to core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/AndroidManifest.xml
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/res/values/values.xml
similarity index 100%
rename from core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
rename to core/tests/overlaytests/remount/test-apps/SharedLibraryOverlay/res/values/values.xml
diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/test-apps/Target/Android.bp
similarity index 77%
rename from core/tests/overlaytests/remount/target/Android.bp
rename to core/tests/overlaytests/remount/test-apps/Target/Android.bp
index 83f9f28..a8910eb 100644
--- a/core/tests/overlaytests/remount/target/Android.bp
+++ b/core/tests/overlaytests/remount/test-apps/Target/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 The Android Open Source Project
+// 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.
@@ -14,7 +14,12 @@
 
 android_test_helper_app {
     name: "OverlayRemountedTest_Target",
-    srcs: ["src/**/*.java"],
     sdk_version: "test_current",
     libs: ["OverlayRemountedTest_SharedLibrary"],
 }
+
+android_test_helper_app {
+    name: "OverlayRemountedTest_TargetUpgrade",
+    resource_dirs: ["res_upgrade"],
+    sdk_version: "test_current",
+}
diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/test-apps/Target/AndroidManifest.xml
similarity index 89%
rename from core/tests/overlaytests/remount/target/AndroidManifest.xml
rename to core/tests/overlaytests/remount/test-apps/Target/AndroidManifest.xml
index dc07dca..d1c7b7e8 100644
--- a/core/tests/overlaytests/remount/target/AndroidManifest.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/AndroidManifest.xml
@@ -19,8 +19,7 @@
           package="com.android.overlaytest.remounted.target">
 
     <application>
-        <uses-library android:name="android.test.runner" />
         <uses-library android:name="com.android.overlaytest.remounted.shared_library"
-                      android:required="true" />
+                      android:required="false" />
     </application>
 </manifest>
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml
similarity index 70%
copy from core/tests/overlaytests/remount/target/res/values/values.xml
copy to core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml
index b5f444a..4aa5bce 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/res/values/overlayable.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,6 +15,10 @@
   ~ limitations under the License.
   -->
 
-<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
-    <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
+<resources>
+    <overlayable name="TestResources">
+        <policy type="public">
+            <item type="bool" name="target_overlaid" />
+        </policy>
+    </overlayable>
 </resources>
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml
similarity index 72%
rename from core/tests/overlaytests/remount/target/res/values/values.xml
rename to core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml
index b5f444a..76253a9 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/res/values/values.xml
@@ -17,4 +17,10 @@
 
 <resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
     <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
+
+    <!-- This resource has a different id in the updated version of this target app to test that the
+         idmap is regenerated when the target is updated. -->
+    <bool name="target_overlaid">false</bool>
+    <public type="bool" name="target_overlaid" id="0x7f010000" />
+    <bool name="target_reference">@bool/target_overlaid</bool>
 </resources>
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml
similarity index 70%
copy from core/tests/overlaytests/remount/target/res/values/values.xml
copy to core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml
index b5f444a..4aa5bce 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/overlayable.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,6 +15,10 @@
   ~ limitations under the License.
   -->
 
-<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
-    <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
+<resources>
+    <overlayable name="TestResources">
+        <policy type="public">
+            <item type="bool" name="target_overlaid" />
+        </policy>
+    </overlayable>
 </resources>
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/values.xml
similarity index 60%
copy from core/tests/overlaytests/remount/target/res/values/values.xml
copy to core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/values.xml
index b5f444a..f552cb0 100644
--- a/core/tests/overlaytests/remount/target/res/values/values.xml
+++ b/core/tests/overlaytests/remount/test-apps/Target/res_upgrade/values/values.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2019 The Android Open Source Project
+  ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -14,7 +14,10 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
-    <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
-</resources>
+<resources>
+    <!-- This resource has a different id in the updated target app than the base target app to test
+         that the idmap is regenerated when the target is updated. -->
+    <bool name="target_overlaid">false</bool>
+    <public type="bool" name="target_overlaid" id="0x7f0100ff" />
+    <bool name="target_reference">@bool/target_overlaid</bool>
+</resources>
\ No newline at end of file
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 59bdf3d..0389639 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -137,6 +137,7 @@
         <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
         <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
         <permission name="android.permission.PACKAGE_USAGE_STATS" />
+        <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
     </privapp-permissions>
 
     <privapp-permissions package="com.android.phone">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 07cf415..18086ec 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -883,6 +883,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-242787066": {
+      "message": "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/WindowContainer.java"
+    },
     "-198463978": {
       "message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b",
       "level": "VERBOSE",
@@ -901,6 +907,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
     },
+    "-172900257": {
+      "message": "addTaskToTargets, target: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/RecentsAnimationController.java"
+    },
     "-167822951": {
       "message": "Attempted to add starting window to token with already existing starting window",
       "level": "WARN",
@@ -1201,6 +1213,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/WindowSurfaceController.java"
     },
+    "315395835": {
+      "message": "Trying to add window with invalid user=%d",
+      "level": "WARN",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "342460966": {
       "message": "DRAG %s: pos=(%d,%d)",
       "level": "INFO",
@@ -1507,12 +1525,30 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "838570988": {
+      "message": "Could not report token removal to the window token client.",
+      "level": "WARN",
+      "group": "WM_ERROR",
+      "at": "com\/android\/server\/wm\/WindowToken.java"
+    },
+    "845234215": {
+      "message": "App is requesting an orientation, return %d for display id=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayContent.java"
+    },
     "853091290": {
       "message": "Moved stack=%s behind stack=%s",
       "level": "DEBUG",
       "group": "WM_DEBUG_RECENTS_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RecentsAnimation.java"
     },
+    "854237232": {
+      "message": "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s",
+      "level": "DEBUG",
+      "group": "WM_DEBUG_RECENTS_ANIMATIONS",
+      "at": "com\/android\/server\/wm\/Task.java"
+    },
     "873914452": {
       "message": "goodToGo()",
       "level": "DEBUG",
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 918e7af..05f4d6b 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -385,7 +385,7 @@
   const StringPiece idmap_data(
       reinterpret_cast<const char*>(idmap_asset->getBuffer(true /*wordAligned*/)),
       static_cast<size_t>(idmap_asset->getLength()));
-  std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_data);
+  std::unique_ptr<const LoadedIdmap> loaded_idmap = LoadedIdmap::Load(idmap_path, idmap_data);
   if (loaded_idmap == nullptr) {
     LOG(ERROR) << "failed to load IDMAP " << idmap_path;
     return {};
@@ -538,8 +538,9 @@
     // Loaders are invalidated by the app, not the system, so assume they are up to date.
     return true;
   }
+  return (!loaded_idmap_ || loaded_idmap_->IsUpToDate()) &&
+      last_mod_time_ == getFileModDate(path_.c_str());
 
-  return last_mod_time_ == getFileModDate(path_.c_str());
 }
 
 }  // namespace android
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 0b2fd9e..eb6ee95 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -20,6 +20,7 @@
 
 #include "android-base/logging.h"
 #include "android-base/stringprintf.h"
+#include "androidfw/misc.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/Util.h"
 #include "utils/ByteOrder.h"
@@ -192,7 +193,9 @@
   return true;
 }
 
-LoadedIdmap::LoadedIdmap(const Idmap_header* header,
+LoadedIdmap::LoadedIdmap(std::string&& idmap_path,
+                         const time_t last_mod_time,
+                         const Idmap_header* header,
                          const Idmap_data_header* data_header,
                          const Idmap_target_entry* target_entries,
                          const Idmap_overlay_entry* overlay_entries,
@@ -201,7 +204,9 @@
        data_header_(data_header),
        target_entries_(target_entries),
        overlay_entries_(overlay_entries),
-       string_pool_(string_pool) {
+       string_pool_(string_pool),
+       idmap_path_(std::move(idmap_path)),
+       idmap_last_mod_time_(last_mod_time) {
 
   size_t length = strnlen(reinterpret_cast<const char*>(header_->overlay_path),
                           arraysize(header_->overlay_path));
@@ -212,7 +217,8 @@
   target_apk_path_.assign(reinterpret_cast<const char*>(header_->target_path), length);
 }
 
-std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_data) {
+std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path,
+                                                     const StringPiece& idmap_data) {
   ATRACE_CALL();
   if (!IsValidIdmapHeader(idmap_data)) {
     return {};
@@ -275,10 +281,14 @@
 
   // Can't use make_unique because LoadedIdmap constructor is private.
   std::unique_ptr<LoadedIdmap> loaded_idmap = std::unique_ptr<LoadedIdmap>(
-      new LoadedIdmap(header, data_header, target_entries, overlay_entries,
-                      idmap_string_pool.release()));
+      new LoadedIdmap(idmap_path.to_string(), getFileModDate(idmap_path.data()), header,
+                      data_header, target_entries, overlay_entries, idmap_string_pool.release()));
 
   return std::move(loaded_idmap);
 }
 
+bool LoadedIdmap::IsUpToDate() const {
+  return idmap_last_mod_time_ == getFileModDate(idmap_path_.c_str());
+}
+
 }  // namespace android
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index ccb57f3..ecc1ce6 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -142,7 +142,13 @@
 class LoadedIdmap {
  public:
   // Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
-  static std::unique_ptr<const LoadedIdmap> Load(const StringPiece& idmap_data);
+  static std::unique_ptr<const LoadedIdmap> Load(const StringPiece& idmap_path,
+                                                 const StringPiece& idmap_data);
+
+  // Returns the path to the IDMAP.
+  inline const std::string& IdmapPath() const {
+    return idmap_path_;
+  }
 
   // Returns the path to the RRO (Runtime Resource Overlay) APK for which this IDMAP was generated.
   inline const std::string& OverlayApkPath() const {
@@ -167,6 +173,10 @@
     return OverlayDynamicRefTable(data_header_, overlay_entries_, target_assigned_package_id);
   }
 
+  // Returns whether the idmap file on disk has not been modified since the construction of this
+  // LoadedIdmap.
+  bool IsUpToDate() const;
+
  protected:
   // Exposed as protected so that tests can subclass and mock this class out.
   LoadedIdmap() = default;
@@ -177,13 +187,17 @@
   const Idmap_overlay_entry* overlay_entries_;
   const std::unique_ptr<ResStringPool> string_pool_;
 
+  const std::string idmap_path_;
   std::string overlay_apk_path_;
   std::string target_apk_path_;
+  const time_t idmap_last_mod_time_;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedIdmap);
 
-  explicit LoadedIdmap(const Idmap_header* header,
+  explicit LoadedIdmap(std::string&& idmap_path,
+                       time_t last_mod_time,
+                       const Idmap_header* header,
                        const Idmap_data_header* data_header,
                        const Idmap_target_entry* target_entries,
                        const Idmap_overlay_entry* overlay_entries,
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index 41ba637..7aa0dbb 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -38,7 +38,7 @@
  protected:
   void SetUp() override {
     // Move to the test data directory so the idmap can locate the overlay APK.
-    std::string original_path = base::GetExecutableDirectory();
+    original_path = base::GetExecutableDirectory();
     chdir(GetTestDataPath().c_str());
 
     system_assets_ = ApkAssets::Load("system/system.apk");
@@ -49,10 +49,14 @@
 
     overlayable_assets_ = ApkAssets::Load("overlayable/overlayable.apk");
     ASSERT_NE(nullptr, overlayable_assets_);
+  }
+
+  void TearDown() override {
     chdir(original_path.c_str());
   }
 
  protected:
+  std::string original_path;
   std::unique_ptr<const ApkAssets> system_assets_;
   std::unique_ptr<const ApkAssets> overlay_assets_;
   std::unique_ptr<const ApkAssets> overlayable_assets_;
@@ -221,8 +225,7 @@
 
 TEST_F(IdmapTest, OverlayLoaderInterop) {
   std::string contents;
-  auto loader_assets = ApkAssets::LoadTable(GetTestDataPath() + "/loader/resources.arsc",
-                                            PROPERTY_LOADER);
+  auto loader_assets = ApkAssets::LoadTable("loader/resources.arsc", PROPERTY_LOADER);
 
   AssetManager2 asset_manager;
   asset_manager.SetApkAssets({overlayable_assets_.get(), loader_assets.get(),
@@ -241,4 +244,25 @@
   ASSERT_EQ(GetStringFromApkAssets(asset_manager, val, cookie), "loader");
 }
 
+TEST_F(IdmapTest, OverlayAssetsIsUpToDate) {
+  std::string idmap_contents;
+  ASSERT_TRUE(base::ReadFileToString("overlay/overlay.idmap", &idmap_contents));
+
+  TemporaryFile temp_file;
+  ASSERT_TRUE(base::WriteStringToFile(idmap_contents, temp_file.path));
+
+  auto apk_assets = ApkAssets::LoadOverlay(temp_file.path);
+  ASSERT_NE(nullptr, apk_assets);
+  ASSERT_TRUE(apk_assets->IsUpToDate());
+
+  unlink(temp_file.path);
+  ASSERT_FALSE(apk_assets->IsUpToDate());
+  sleep(2);
+
+  base::WriteStringToFile("hello", temp_file.path);
+  sleep(2);
+
+  ASSERT_FALSE(apk_assets->IsUpToDate());
+}
+
 }  // namespace
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 4150926..75ea0cb 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -47,10 +47,10 @@
     Location getLastLocation(in LocationRequest request, String packageName, String featureId);
     boolean getCurrentLocation(in LocationRequest request,
             in ICancellationSignal cancellationSignal, in ILocationListener listener,
-            String packageName, String featureId);
+            String packageName, String featureId, String listenerId);
 
     void requestLocationUpdates(in LocationRequest request, in ILocationListener listener,
-            in PendingIntent intent, String packageName, String featureId);
+            in PendingIntent intent, String packageName, String featureId, String listenerId);
     void removeUpdates(in ILocationListener listener, in PendingIntent intent);
 
     void requestGeofence(in LocationRequest request, in Geofence geofence,
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index fcbd3e5..d1b41df 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -718,7 +718,7 @@
             currentLocationRequest.setExpireIn(GET_CURRENT_LOCATION_MAX_TIMEOUT_MS);
         }
 
-        GetCurrentLocationTransport listenerTransport = new GetCurrentLocationTransport(executor,
+        GetCurrentLocationTransport transport = new GetCurrentLocationTransport(executor,
                 consumer);
 
         if (cancellationSignal != null) {
@@ -729,14 +729,15 @@
 
         try {
             if (mService.getCurrentLocation(currentLocationRequest, remoteCancellationSignal,
-                    listenerTransport, mContext.getPackageName(), mContext.getAttributionTag())) {
-                listenerTransport.register(mContext.getSystemService(AlarmManager.class),
+                    transport, mContext.getPackageName(), mContext.getAttributionTag(),
+                    transport.getListenerId())) {
+                transport.register(mContext.getSystemService(AlarmManager.class),
                         remoteCancellationSignal);
                 if (cancellationSignal != null) {
-                    cancellationSignal.setOnCancelListener(listenerTransport::cancel);
+                    cancellationSignal.setOnCancelListener(transport::cancel);
                 }
             } else {
-                listenerTransport.fail();
+                transport.fail();
             }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1175,7 +1176,8 @@
             boolean registered = false;
             try {
                 mService.requestLocationUpdates(locationRequest, transport, null,
-                        mContext.getPackageName(), mContext.getAttributionTag());
+                        mContext.getPackageName(), mContext.getAttributionTag(),
+                        transport.getListenerId());
                 registered = true;
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
@@ -1220,7 +1222,7 @@
 
         try {
             mService.requestLocationUpdates(locationRequest, null, pendingIntent,
-                    mContext.getPackageName(), mContext.getAttributionTag());
+                    mContext.getPackageName(), mContext.getAttributionTag(), null);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2558,6 +2560,10 @@
             mRemoteCancellationSignal = null;
         }
 
+        public String getListenerId() {
+            return mConsumer.getClass().getName() + "@" + System.identityHashCode(mConsumer);
+        }
+
         public synchronized void register(AlarmManager alarmManager,
                 ICancellationSignal remoteCancellationSignal) {
             if (mConsumer == null) {
@@ -2683,6 +2689,10 @@
             return mListener;
         }
 
+        public String getListenerId() {
+            return mListener.getClass().getName() + "@" + System.identityHashCode(mListener);
+        }
+
         public void register(@NonNull Executor executor) {
             Preconditions.checkArgument(executor != null, "invalid null executor");
             mExecutor = executor;
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index 4dd1a29..5f0acc8 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -264,8 +264,8 @@
                 /* numUpdates= */ Integer.MAX_VALUE,
                 /* smallestDisplacement= */ 0,
                 /* hideFromAppOps= */ false,
-                /* lowPowerMode= */ false,
                 /* locationSettingsIgnored= */ false,
+                /* lowPowerMode= */ false,
                 /* workSource= */ null);
     }
 
@@ -282,8 +282,8 @@
                 src.mNumUpdates,
                 src.mSmallestDisplacement,
                 src.mHideFromAppOps,
-                src.mLowPowerMode,
                 src.mLocationSettingsIgnored,
+                src.mLowPowerMode,
                 src.mWorkSource);
     }
 
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index fc9b91c..a31f177 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -676,8 +676,6 @@
     if (buffer->size() > 0) {
         std::shared_ptr<C2Buffer> c2Buffer = buffer->asC2Buffer();
         if (c2Buffer) {
-            // asC2Buffer clears internal reference, so set the reference again.
-            buffer->copy(c2Buffer);
             switch (c2Buffer->data().type()) {
                 case C2BufferData::LINEAR: {
                     std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock};
@@ -2526,7 +2524,7 @@
     codec->selectAudioPresentation((int32_t)presentationId, (int32_t)programId);
 }
 
-static void android_media_MediaCodec_native_init(JNIEnv *env) {
+static void android_media_MediaCodec_native_init(JNIEnv *env, jclass) {
     ScopedLocalRef<jclass> clazz(
             env, env->FindClass("android/media/MediaCodec"));
     CHECK(clazz.get() != NULL);
@@ -2983,7 +2981,7 @@
 }
 
 static jboolean android_media_MediaCodec_LinearBlock_checkCompatible(
-        JNIEnv *env, jobjectArray codecNames) {
+        JNIEnv *env, jclass, jobjectArray codecNames) {
     std::vector<std::string> names;
     PopulateNamesVector(env, codecNames, &names);
     bool isCompatible = false;
diff --git a/packages/CarSystemUI/res/layout/notification_center_activity.xml b/packages/CarSystemUI/res/layout/notification_center_activity.xml
index 4fef489..0af74c4 100644
--- a/packages/CarSystemUI/res/layout/notification_center_activity.xml
+++ b/packages/CarSystemUI/res/layout/notification_center_activity.xml
@@ -20,8 +20,6 @@
     android:id="@+id/notification_view"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:visibility="invisible"
-    android:layout_marginBottom="@dimen/navigation_bar_height"
     android:background="@color/notification_shade_background_color">
 
     <View
diff --git a/packages/CarSystemUI/res/layout/notification_panel_container.xml b/packages/CarSystemUI/res/layout/notification_panel_container.xml
index bf71396..3b53c6a 100644
--- a/packages/CarSystemUI/res/layout/notification_panel_container.xml
+++ b/packages/CarSystemUI/res/layout/notification_panel_container.xml
@@ -18,4 +18,5 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/notification_container"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"/>
+    android:layout_height="match_parent"
+    android:visibility="invisible"/>
diff --git a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
index 1b0a211..067e359 100644
--- a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
+++ b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml
@@ -22,10 +22,12 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
+    <!-- TODO(b/151617493): replace marginBottom with insets. -->
     <ViewStub android:id="@+id/notification_panel_stub"
               android:layout_width="match_parent"
               android:layout_height="match_parent"
-              android:layout="@layout/notification_panel_container"/>
+              android:layout="@layout/notification_panel_container"
+              android:layout_marginBottom="@dimen/navigation_bar_height"/>
 
     <ViewStub android:id="@+id/fullscreen_user_switcher_stub"
               android:layout_width="match_parent"
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java
index b057198..44e43fe 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarDeviceProvisionedController.java
@@ -24,7 +24,7 @@
  */
 public interface CarDeviceProvisionedController extends DeviceProvisionedController {
     /**
-     * Returns {@code true} then SUW is in progress for the given user.
+     * Returns {@code true} when SUW is in progress for the given user.
      */
     boolean isUserSetupInProgress(int user);
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
index 1901a2d..d8a894c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java
@@ -16,9 +16,6 @@
 
 package com.android.systemui.car.notification;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
 import android.app.ActivityManager;
 import android.car.Car;
 import android.car.drivingstate.CarUxRestrictionsManager;
@@ -33,7 +30,6 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
@@ -54,7 +50,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.window.OverlayViewController;
+import com.android.systemui.window.OverlayPanelViewController;
 import com.android.systemui.window.OverlayViewGlobalStateController;
 
 import javax.inject.Inject;
@@ -62,39 +58,22 @@
 
 /** View controller for the notification panel. */
 @Singleton
-public class NotificationPanelViewController extends OverlayViewController {
+public class NotificationPanelViewController extends OverlayPanelViewController {
 
-    // used to calculate how fast to open or close the window
-    private static final float DEFAULT_FLING_VELOCITY = 0;
-    // max time a fling animation takes
-    private static final float FLING_ANIMATION_MAX_TIME = 0.5f;
-    // acceleration rate for the fling animation
-    private static final float FLING_SPEED_UP_FACTOR = 0.6f;
-
-    private static final int SWIPE_DOWN_MIN_DISTANCE = 25;
-    private static final int SWIPE_MAX_OFF_PATH = 75;
-    private static final int SWIPE_THRESHOLD_VELOCITY = 200;
     private static final boolean DEBUG = true;
     private static final String TAG = "NotificationPanelViewController";
 
     private final Context mContext;
     private final Resources mResources;
     private final CarServiceProvider mCarServiceProvider;
-    private final CarDeviceProvisionedController mCarDeviceProvisionedController;
     private final IStatusBarService mBarService;
     private final CommandQueue mCommandQueue;
     private final NotificationDataManager mNotificationDataManager;
     private final CarUxRestrictionManagerWrapper mCarUxRestrictionManagerWrapper;
     private final CarNotificationListener mCarNotificationListener;
     private final NotificationClickHandlerFactory mNotificationClickHandlerFactory;
-    private final FlingAnimationUtils mFlingAnimationUtils;
     private final StatusBarStateController mStatusBarStateController;
 
-    private final int mSettleClosePercentage;
-
-    private float mOpeningVelocity = DEFAULT_FLING_VELOCITY;
-    private float mClosingVelocity = DEFAULT_FLING_VELOCITY;
-
     private float mInitialBackgroundAlpha;
     private float mBackgroundAlphaDiff;
 
@@ -108,13 +87,7 @@
     private float mFirstTouchDownOnGlassPane;
     private boolean mNotificationListAtBottomAtTimeOfTouch;
     private boolean mIsSwipingVerticallyToClose;
-    private int mPercentageFromBottom;
-    private boolean mIsNotificationAnimating;
     private boolean mIsNotificationCardSwiping;
-    private boolean mPanelExpanded = false;
-
-    private View.OnTouchListener mTopNavBarNotificationTouchListener;
-    private View.OnTouchListener mNavBarNotificationTouchListener;
 
     private OnUnseenCountUpdateListener mUnseenCountUpdateListener;
 
@@ -123,6 +96,7 @@
             Context context,
             @Main Resources resources,
             OverlayViewGlobalStateController overlayViewGlobalStateController,
+            FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
 
             /* Other things */
             CarServiceProvider carServiceProvider,
@@ -135,26 +109,21 @@
             CarUxRestrictionManagerWrapper carUxRestrictionManagerWrapper,
             CarNotificationListener carNotificationListener,
             NotificationClickHandlerFactory notificationClickHandlerFactory,
-            FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
 
             /* Things that need to be replaced */
             StatusBarStateController statusBarStateController
     ) {
-        super(R.id.notification_panel_stub, overlayViewGlobalStateController);
+        super(context, resources, R.id.notification_panel_stub, overlayViewGlobalStateController,
+                flingAnimationUtilsBuilder, carDeviceProvisionedController);
         mContext = context;
         mResources = resources;
         mCarServiceProvider = carServiceProvider;
-        mCarDeviceProvisionedController = carDeviceProvisionedController;
         mBarService = barService;
         mCommandQueue = commandQueue;
         mNotificationDataManager = notificationDataManager;
         mCarUxRestrictionManagerWrapper = carUxRestrictionManagerWrapper;
         mCarNotificationListener = carNotificationListener;
         mNotificationClickHandlerFactory = notificationClickHandlerFactory;
-        mFlingAnimationUtils = flingAnimationUtilsBuilder
-                .setMaxLengthSeconds(FLING_ANIMATION_MAX_TIME)
-                .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
-                .build();
         mStatusBarStateController = statusBarStateController;
 
         // Notification background setup.
@@ -175,60 +144,6 @@
                             + " percentage");
         }
         mBackgroundAlphaDiff = finalBackgroundAlpha - mInitialBackgroundAlpha;
-
-        // Notification Panel param setup
-        mSettleClosePercentage = mResources.getInteger(
-                R.integer.notification_settle_close_percentage);
-
-        // Attached to the top navigation bar (i.e. status bar) to detect pull down of the
-        // notification shade.
-        GestureDetector openGestureDetector = new GestureDetector(mContext,
-                new OpenNotificationGestureListener() {
-                    @Override
-                    protected void openNotification() {
-                        animateExpandNotificationsPanel();
-                    }
-                });
-
-        // Attached to the NavBars to close the notification shade
-        GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(mContext,
-                new NavBarCloseNotificationGestureListener() {
-                    @Override
-                    protected void close() {
-                        if (mPanelExpanded) {
-                            animateCollapsePanels();
-                        }
-                    }
-                });
-
-        mTopNavBarNotificationTouchListener = (v, event) -> {
-            if (!isInflated()) {
-                getOverlayViewGlobalStateController().inflateView(this);
-            }
-            if (!mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
-                return true;
-            }
-
-            boolean consumed = openGestureDetector.onTouchEvent(event);
-            if (consumed) {
-                return true;
-            }
-            maybeCompleteAnimation(event);
-            return true;
-        };
-
-        mNavBarNotificationTouchListener =
-                (v, event) -> {
-                    if (!isInflated()) {
-                        return true;
-                    }
-                    boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
-                    if (consumed) {
-                        return true;
-                    }
-                    maybeCompleteAnimation(event);
-                    return true;
-                };
     }
 
     @Override
@@ -252,14 +167,13 @@
     private void onNotificationViewInflated() {
         // Find views.
         mNotificationView = getLayout().findViewById(R.id.notification_view);
-        View glassPane = mNotificationView.findViewById(R.id.glass_pane);
-        mHandleBar = mNotificationView.findViewById(R.id.handle_bar);
-        mNotificationList = mNotificationView.findViewById(R.id.notifications);
+        setupHandleBar();
+        setupNotificationPanel();
 
         mNotificationClickHandlerFactory.registerClickListener((launchResult, alertEntry) -> {
             if (launchResult == ActivityManager.START_TASK_TO_FRONT
                     || launchResult == ActivityManager.START_SUCCESS) {
-                animateCollapsePanels();
+                animateCollapsePanel();
             }
         });
 
@@ -269,39 +183,52 @@
                         mNotificationDataManager.getUnseenNotificationCount());
             }
         });
+
         mNotificationClickHandlerFactory.setNotificationDataManager(mNotificationDataManager);
         mNotificationView.setClickHandlerFactory(mNotificationClickHandlerFactory);
         mNotificationView.setNotificationDataManager(mNotificationDataManager);
 
-        mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() {
-            @Override
-            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
-                super.onScrolled(recyclerView, dx, dy);
-                if (!mNotificationList.canScrollVertically(1)) {
-                    mNotificationListAtBottom = true;
-                    return;
-                }
-                mNotificationListAtBottom = false;
-                mIsSwipingVerticallyToClose = false;
-                mNotificationListAtBottomAtTimeOfTouch = false;
-            }
-        });
+        mCarServiceProvider.addListener(car -> {
+            CarUxRestrictionsManager carUxRestrictionsManager =
+                    (CarUxRestrictionsManager)
+                            car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
+            mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager(
+                    carUxRestrictionsManager);
 
-        // Attached to the notification ui to detect close request of the notification shade.
+            mNotificationViewController = new NotificationViewController(
+                    mNotificationView,
+                    PreprocessingManager.getInstance(mContext),
+                    mCarNotificationListener,
+                    mCarUxRestrictionManagerWrapper,
+                    mNotificationDataManager);
+            mNotificationViewController.enable();
+        });
+    }
+
+    private void setupHandleBar() {
+        mHandleBar = mNotificationView.findViewById(R.id.handle_bar);
+        GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
+                new HandleBarCloseGestureListener());
+        mHandleBar.setOnTouchListener((v, event) -> {
+            handleBarCloseNotificationGestureDetector.onTouchEvent(event);
+            maybeCompleteAnimation(event);
+            return true;
+        });
+    }
+
+    private void setupNotificationPanel() {
+        View glassPane = mNotificationView.findViewById(R.id.glass_pane);
+        mNotificationList = mNotificationView.findViewById(R.id.notifications);
         GestureDetector closeGestureDetector = new GestureDetector(mContext,
-                new CloseNotificationGestureListener() {
+                new CloseGestureListener() {
                     @Override
                     protected void close() {
-                        if (mPanelExpanded) {
-                            animateCollapsePanels();
+                        if (isPanelExpanded()) {
+                            animateCollapsePanel();
                         }
                     }
                 });
 
-        // Attached to the Handle bar to close the notification shade
-        GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
-                new HandleBarCloseNotificationGestureListener());
-
         // The glass pane is used to view touch events before passed to the notification list.
         // This allows us to initialize gesture listeners and detect when to close the notifications
         glassPane.setOnTouchListener((v, event) -> {
@@ -320,6 +247,21 @@
             return false;
         });
 
+        mNotificationList.addOnScrollListener(new RecyclerView.OnScrollListener() {
+            @Override
+            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+                super.onScrolled(recyclerView, dx, dy);
+                // Check if we can scroll vertically downwards.
+                if (!mNotificationList.canScrollVertically(/* direction= */ 1)) {
+                    mNotificationListAtBottom = true;
+                    return;
+                }
+                mNotificationListAtBottom = false;
+                mIsSwipingVerticallyToClose = false;
+                mNotificationListAtBottomAtTimeOfTouch = false;
+            }
+        });
+
         mNotificationList.setOnTouchListener((v, event) -> {
             mIsNotificationCardSwiping = Math.abs(mFirstTouchDownOnGlassPane - event.getRawX())
                     > SWIPE_MAX_OFF_PATH;
@@ -341,19 +283,19 @@
 
             boolean handled = closeGestureDetector.onTouchEvent(event);
             boolean isTracking = mIsTracking;
-            Rect rect = mNotificationView.getClipBounds();
+            Rect rect = getLayout().getClipBounds();
             float clippedHeight = 0;
             if (rect != null) {
                 clippedHeight = rect.bottom;
             }
             if (!handled && event.getActionMasked() == MotionEvent.ACTION_UP
                     && mIsSwipingVerticallyToClose) {
-                if (mSettleClosePercentage < mPercentageFromBottom && isTracking) {
-                    animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
-                } else if (clippedHeight != mNotificationView.getHeight() && isTracking) {
+                if (getSettleClosePercentage() < getPercentageFromBottom() && isTracking) {
+                    animatePanel(DEFAULT_FLING_VELOCITY, false);
+                } else if (clippedHeight != getLayout().getHeight() && isTracking) {
                     // this can be caused when user is at the end of the list and trying to
                     // fling to top of the list by scrolling down.
-                    animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
+                    animatePanel(DEFAULT_FLING_VELOCITY, true);
                 }
             }
 
@@ -365,28 +307,6 @@
             }
             return handled || isTracking;
         });
-
-        mCarServiceProvider.addListener(car -> {
-            CarUxRestrictionsManager carUxRestrictionsManager =
-                    (CarUxRestrictionsManager)
-                            car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
-            mCarUxRestrictionManagerWrapper.setCarUxRestrictionsManager(
-                    carUxRestrictionsManager);
-
-            mNotificationViewController = new NotificationViewController(
-                    mNotificationView,
-                    PreprocessingManager.getInstance(mContext),
-                    mCarNotificationListener,
-                    mCarUxRestrictionManagerWrapper,
-                    mNotificationDataManager);
-            mNotificationViewController.enable();
-        });
-
-        mHandleBar.setOnTouchListener((v, event) -> {
-            handleBarCloseNotificationGestureDetector.onTouchEvent(event);
-            maybeCompleteAnimation(event);
-            return true;
-        });
     }
 
     /** Called when the car power state is changed to ON. */
@@ -397,139 +317,40 @@
         mNotificationDataManager.clearAll();
     }
 
-    View.OnTouchListener getTopNavBarNotificationTouchListener() {
-        return mTopNavBarNotificationTouchListener;
+    @Override
+    protected boolean shouldAnimateCollapsePanel() {
+        return true;
     }
 
-    View.OnTouchListener getNavBarNotificationTouchListener() {
-        return mNavBarNotificationTouchListener;
+    @Override
+    protected void onAnimateCollapsePanel() {
+        // No op.
     }
 
-    private void maybeCompleteAnimation(MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_UP
-                && mNotificationView.getVisibility() == View.VISIBLE) {
-            if (mSettleClosePercentage < mPercentageFromBottom) {
-                animateNotificationPanel(DEFAULT_FLING_VELOCITY, false);
-            } else {
-                animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
-            }
-        }
+    @Override
+    protected boolean shouldAnimateExpandPanel() {
+        return mCommandQueue.panelsEnabled();
     }
 
-    /**
-     * Animates the notification shade from one position to other. This is used to either open or
-     * close the notification shade completely with a velocity. If the animation is to close the
-     * notification shade this method also makes the view invisible after animation ends.
-     */
-    private void animateNotificationPanel(float velocity, boolean isClosing) {
-        float to = 0;
-        if (!isClosing) {
-            to = mNotificationView.getHeight();
-        }
-
-        Rect rect = mNotificationView.getClipBounds();
-        if (rect != null && rect.bottom != to) {
-            float from = rect.bottom;
-            animate(from, to, velocity, isClosing);
-            return;
-        }
-
-        // We will only be here if the shade is being opened programmatically or via button when
-        // height of the layout was not calculated.
-        ViewTreeObserver notificationTreeObserver = mNotificationView.getViewTreeObserver();
-        notificationTreeObserver.addOnGlobalLayoutListener(
-                new ViewTreeObserver.OnGlobalLayoutListener() {
-                    @Override
-                    public void onGlobalLayout() {
-                        ViewTreeObserver obs = mNotificationView.getViewTreeObserver();
-                        obs.removeOnGlobalLayoutListener(this);
-                        float to = mNotificationView.getHeight();
-                        animate(/* from= */ 0, to, velocity, isClosing);
-                    }
-                });
-    }
-
-    private void animateCollapsePanels() {
-        if (!mPanelExpanded || mNotificationView.getVisibility() == View.INVISIBLE) {
-            return;
-        }
-        getOverlayViewGlobalStateController().setWindowFocusable(false);
-        animateNotificationPanel(mClosingVelocity, true);
-    }
-
-    private void animateExpandNotificationsPanel() {
-        if (!mCommandQueue.panelsEnabled()
-                || !mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
-            return;
-        }
-        // scroll to top
+    @Override
+    protected void onAnimateExpandPanel() {
         mNotificationList.scrollToPosition(0);
-        setPanelVisible(true);
-        mNotificationView.setVisibility(View.VISIBLE);
-        animateNotificationPanel(mOpeningVelocity, false);
-
-        setPanelExpanded(true);
     }
 
-    private void animate(float from, float to, float velocity, boolean isClosing) {
-        if (mIsNotificationAnimating) {
-            return;
-        }
-        mIsNotificationAnimating = true;
-        mIsTracking = true;
-        ValueAnimator animator = ValueAnimator.ofFloat(from, to);
-        animator.addUpdateListener(
-                animation -> {
-                    float animatedValue = (Float) animation.getAnimatedValue();
-                    setNotificationViewClipBounds((int) animatedValue);
-                });
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
-                mIsNotificationAnimating = false;
-                mIsTracking = false;
-                mOpeningVelocity = DEFAULT_FLING_VELOCITY;
-                mClosingVelocity = DEFAULT_FLING_VELOCITY;
-                if (isClosing) {
-                    setPanelVisible(false);
-                    mNotificationView.setVisibility(View.INVISIBLE);
-                    mNotificationView.setClipBounds(null);
-                    mNotificationViewController.onVisibilityChanged(false);
-                    // let the status bar know that the panel is closed
-                    setPanelExpanded(false);
-                } else {
-                    mNotificationViewController.onVisibilityChanged(true);
-                    // let the status bar know that the panel is open
-                    mNotificationView.setVisibleNotificationsAsSeen();
-                    setPanelExpanded(true);
-                }
-            }
-        });
-        mFlingAnimationUtils.apply(animator, from, to, Math.abs(velocity));
-        animator.start();
+    @Override
+    protected void onCollapseAnimationEnd() {
+        mNotificationViewController.onVisibilityChanged(false);
     }
 
-    /**
-     * Set the panel view to be visible.
-     */
-    public void setPanelVisible(boolean visible) {
-        if (visible && !getOverlayViewGlobalStateController().isWindowVisible()) {
-            getOverlayViewGlobalStateController().setWindowVisible(true);
-        }
-        if (!visible && getOverlayViewGlobalStateController().isWindowVisible()) {
-            getOverlayViewGlobalStateController().setWindowVisible(false);
-        }
-        getLayout().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
-        getOverlayViewGlobalStateController().setWindowFocusable(visible);
+    @Override
+    protected void onExpandAnimationEnd() {
+        mNotificationViewController.onVisibilityChanged(true);
+        mNotificationView.setVisibleNotificationsAsSeen();
     }
 
-    /**
-     * Set the panel state to expanded. This will expand or collapse the overlay window if
-     * necessary.
-     */
-    public void setPanelExpanded(boolean expand) {
-        mPanelExpanded = expand;
+    @Override
+    protected void onPanelExpanded(boolean expand) {
+        super.onPanelExpanded(expand);
 
         if (expand && mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
             if (DEBUG) {
@@ -550,19 +371,19 @@
         }
     }
 
-    private void setNotificationViewClipBounds(int height) {
-        if (height > mNotificationView.getHeight()) {
-            height = mNotificationView.getHeight();
-        }
-        Rect clipBounds = new Rect();
-        clipBounds.set(0, 0, mNotificationView.getWidth(), height);
-        // Sets the clip region on the notification list view.
-        mNotificationView.setClipBounds(clipBounds);
+    @Override
+    protected void onOpenScrollStart() {
+        mNotificationList.scrollToPosition(0);
+    }
+
+    @Override
+    protected void onScroll(int height) {
         if (mHandleBar != null) {
             ViewGroup.MarginLayoutParams lp =
                     (ViewGroup.MarginLayoutParams) mHandleBar.getLayoutParams();
             mHandleBar.setTranslationY(height - mHandleBar.getHeight() - lp.bottomMargin);
         }
+
         if (mNotificationView.getHeight() > 0) {
             Drawable background = mNotificationView.getBackground().mutate();
             background.setAlpha((int) (getBackgroundAlpha(height) * 255));
@@ -570,6 +391,13 @@
         }
     }
 
+    @Override
+    protected boolean shouldAllowClosingScroll() {
+        // Unless the notification list is at the bottom, the panel shouldn't be allowed to
+        // collapse on scroll.
+        return mNotificationListAtBottomAtTimeOfTouch;
+    }
+
     /**
      * Calculates the alpha value for the background based on how much of the notification
      * shade is visible to the user. When the notification shade is completely open then
@@ -580,30 +408,6 @@
                 + ((float) height / mNotificationView.getHeight() * mBackgroundAlphaDiff);
     }
 
-    private void calculatePercentageFromBottom(float height) {
-        if (mNotificationView.getHeight() > 0) {
-            mPercentageFromBottom = (int) Math.abs(
-                    height / mNotificationView.getHeight() * 100);
-        }
-    }
-
-    /** Toggles the visibility of the notification panel. */
-    public void toggle() {
-        if (!isInflated()) {
-            getOverlayViewGlobalStateController().inflateView(this);
-        }
-        if (mPanelExpanded) {
-            animateCollapsePanels();
-        } else {
-            animateExpandNotificationsPanel();
-        }
-    }
-
-    /** Returns {@code true} if the notification panel is expanded. */
-    public boolean isPanelExpanded() {
-        return mPanelExpanded;
-    }
-
     /** Sets the unseen count listener. */
     public void setOnUnseenCountUpdateListener(OnUnseenCountUpdateListener listener) {
         mUnseenCountUpdateListener = listener;
@@ -619,154 +423,9 @@
     }
 
     /**
-     * Only responsible for open hooks. Since once the panel opens it covers all elements
-     * there is no need to merge with close.
-     */
-    private abstract class OpenNotificationGestureListener extends
-            GestureDetector.SimpleOnGestureListener {
-
-        @Override
-        public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
-                float distanceY) {
-
-            if (mNotificationView.getVisibility() == View.INVISIBLE) {
-                // when the on-scroll is called for the first time to open.
-                mNotificationList.scrollToPosition(0);
-            }
-            setPanelVisible(true);
-            mNotificationView.setVisibility(View.VISIBLE);
-
-            // clips the view for the notification shade when the user scrolls to open.
-            setNotificationViewClipBounds((int) event2.getRawY());
-
-            // Initially the scroll starts with height being zero. This checks protects from divide
-            // by zero error.
-            calculatePercentageFromBottom(event2.getRawY());
-
-            mIsTracking = true;
-            return true;
-        }
-
-
-        @Override
-        public boolean onFling(MotionEvent event1, MotionEvent event2,
-                float velocityX, float velocityY) {
-            if (velocityY > SWIPE_THRESHOLD_VELOCITY) {
-                mOpeningVelocity = velocityY;
-                openNotification();
-                return true;
-            }
-            animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
-
-            return false;
-        }
-
-        protected abstract void openNotification();
-    }
-
-    /**
-     * To be installed on the open panel notification panel
-     */
-    private abstract class CloseNotificationGestureListener extends
-            GestureDetector.SimpleOnGestureListener {
-
-        @Override
-        public boolean onSingleTapUp(MotionEvent motionEvent) {
-            if (mPanelExpanded) {
-                animateNotificationPanel(DEFAULT_FLING_VELOCITY, true);
-            }
-            return true;
-        }
-
-        @Override
-        public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
-                float distanceY) {
-            // should not clip while scroll to the bottom of the list.
-            if (!mNotificationListAtBottomAtTimeOfTouch) {
-                return false;
-            }
-            float actualNotificationHeight =
-                    mNotificationView.getHeight() - (event1.getRawY() - event2.getRawY());
-            if (actualNotificationHeight > mNotificationView.getHeight()) {
-                actualNotificationHeight = mNotificationView.getHeight();
-            }
-            if (mNotificationView.getHeight() > 0) {
-                mPercentageFromBottom = (int) Math.abs(
-                        actualNotificationHeight / mNotificationView.getHeight() * 100);
-                boolean isUp = distanceY > 0;
-
-                // This check is to figure out if onScroll was called while swiping the card at
-                // bottom of the list. At that time we should not allow notification shade to
-                // close. We are also checking for the upwards swipe gesture here because it is
-                // possible if a user is closing the notification shade and while swiping starts
-                // to open again but does not fling. At that time we should allow the
-                // notification shade to close fully or else it would stuck in between.
-                if (Math.abs(mNotificationView.getHeight() - actualNotificationHeight)
-                        > SWIPE_DOWN_MIN_DISTANCE && isUp) {
-                    setNotificationViewClipBounds((int) actualNotificationHeight);
-                    mIsTracking = true;
-                } else if (!isUp) {
-                    setNotificationViewClipBounds((int) actualNotificationHeight);
-                }
-            }
-            // if we return true the items in RV won't be scrollable.
-            return false;
-        }
-
-
-        @Override
-        public boolean onFling(MotionEvent event1, MotionEvent event2,
-                float velocityX, float velocityY) {
-            // should not fling if the touch does not start when view is at the bottom of the list.
-            if (!mNotificationListAtBottomAtTimeOfTouch) {
-                return false;
-            }
-            if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH
-                    || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
-                // swipe was not vertical or was not fast enough
-                return false;
-            }
-            boolean isUp = velocityY < 0;
-            if (isUp) {
-                close();
-                return true;
-            } else {
-                // we should close the shade
-                animateNotificationPanel(velocityY, false);
-            }
-            return false;
-        }
-
-        protected abstract void close();
-    }
-
-    /**
-     * To be installed on the nav bars.
-     */
-    private abstract class NavBarCloseNotificationGestureListener extends
-            CloseNotificationGestureListener {
-        @Override
-        public boolean onSingleTapUp(MotionEvent e) {
-            mClosingVelocity = DEFAULT_FLING_VELOCITY;
-            if (mPanelExpanded) {
-                close();
-            }
-            return super.onSingleTapUp(e);
-        }
-
-        @Override
-        public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
-                float distanceY) {
-            calculatePercentageFromBottom(event2.getRawY());
-            setNotificationViewClipBounds((int) event2.getRawY());
-            return true;
-        }
-    }
-
-    /**
      * To be installed on the handle bar.
      */
-    private class HandleBarCloseNotificationGestureListener extends
+    private class HandleBarCloseGestureListener extends
             GestureDetector.SimpleOnGestureListener {
 
         @Override
@@ -777,9 +436,8 @@
             // the handle bar we should calculate the height using the diff of event1 and event2.
             // This will help the notification shade to clip smoothly as the event2 value changes
             // as event1 value will be fixed.
-            int clipHeight =
-                    mNotificationView.getHeight() - (int) (event1.getRawY() - event2.getRawY());
-            setNotificationViewClipBounds(clipHeight);
+            int clipHeight = getLayout().getHeight() - (int) (event1.getRawY() - event2.getRawY());
+            setViewClipBounds(clipHeight);
             return true;
         }
     }
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
index 110c2ee..9d71797 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewMediator.java
@@ -59,13 +59,13 @@
     @Override
     public void registerListeners() {
         mCarNavigationBarController.registerTopBarTouchListener(
-                mNotificationPanelViewController.getTopNavBarNotificationTouchListener());
+                mNotificationPanelViewController.getDragOpenTouchListener());
         mCarNavigationBarController.registerBottomBarTouchListener(
-                mNotificationPanelViewController.getNavBarNotificationTouchListener());
+                mNotificationPanelViewController.getDragCloseTouchListener());
         mCarNavigationBarController.registerLeftBarTouchListener(
-                mNotificationPanelViewController.getNavBarNotificationTouchListener());
+                mNotificationPanelViewController.getDragCloseTouchListener());
         mCarNavigationBarController.registerRightBarTouchListener(
-                mNotificationPanelViewController.getNavBarNotificationTouchListener());
+                mNotificationPanelViewController.getDragCloseTouchListener());
 
         mCarNavigationBarController.registerNotificationController(
                 new CarNavigationBarController.NotificationsShadeController() {
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/CarTrustAgentUnlockDialogHelper.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/CarTrustAgentUnlockDialogHelper.java
deleted file mode 100644
index 5977165..0000000
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/CarTrustAgentUnlockDialogHelper.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.car.userswitcher;
-
-import android.app.admin.DevicePolicyManager;
-import android.bluetooth.BluetoothAdapter;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.R;
-import com.android.systemui.dagger.qualifiers.Main;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * A helper class displays an unlock dialog and receives broadcast about detecting trusted device
- * & unlocking state to show the appropriate message on the dialog.
- */
-@Singleton
-class CarTrustAgentUnlockDialogHelper extends BroadcastReceiver{
-    private static final String TAG = CarTrustAgentUnlockDialogHelper.class.getSimpleName();
-
-    private final Context mContext;
-    private final Resources mResources;
-    private final WindowManager mWindowManager;
-    private final UserManager mUserManager;
-    private final WindowManager.LayoutParams mParams;
-    /**
-     * Not using Dialog because context passed from {@link FullscreenUserSwitcherViewMediator}
-     * is not an activity.
-     */
-    private final View mUnlockDialogLayout;
-    private final TextView mUnlockingText;
-    private final Button mButton;
-    private final IntentFilter mFilter;
-    private int mUid;
-    private boolean mIsDialogShowing;
-    private OnHideListener mOnHideListener;
-
-    @Inject
-    CarTrustAgentUnlockDialogHelper(Context context, @Main Resources resources,
-            UserManager userManager, WindowManager windowManager) {
-        mContext = context;
-        mResources = resources;
-        mUserManager = userManager;
-        mWindowManager = windowManager;
-        mParams = createLayoutParams();
-        mFilter = getIntentFilter();
-
-        mParams.packageName = mContext.getPackageName();
-        mParams.setTitle(mContext.getString(R.string.unlock_dialog_title));
-
-        mUnlockDialogLayout = LayoutInflater.from(mContext).inflate(
-            R.layout.trust_agent_unlock_dialog, null);
-        mUnlockDialogLayout.setLayoutParams(mParams);
-
-        View dialogParent = mUnlockDialogLayout.findViewById(R.id.unlock_dialog_parent);
-        dialogParent.setOnTouchListener((v, event)-> {
-            hideUnlockDialog(/* dismissUserSwitcher= */ false);
-            return true;
-        });
-        View unlockDialog = mUnlockDialogLayout.findViewById(R.id.unlock_dialog);
-        unlockDialog.setOnTouchListener((v, event) -> {
-            // If the person taps inside the unlock dialog, the touch event will be intercepted here
-            // and the dialog will not exit
-            return true;
-        });
-        mUnlockingText = mUnlockDialogLayout.findViewById(R.id.unlocking_text);
-        mButton = mUnlockDialogLayout.findViewById(R.id.enter_pin_button);
-        mButton.setOnClickListener(v -> {
-            hideUnlockDialog(/* dismissUserSwitcher= */true);
-            // TODO(b/138250105) Stop unlock advertising
-        });
-
-        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-        if (bluetoothAdapter != null
-                && bluetoothAdapter.getLeState() == BluetoothAdapter.STATE_BLE_ON) {
-            mUnlockingText.setText(R.string.unlock_dialog_message_start);
-        }
-    }
-
-    /**
-     * This filter is listening on:
-     * {@link BluetoothAdapter#ACTION_BLE_STATE_CHANGED} for starting unlock advertising;
-     * {@link Intent#ACTION_USER_UNLOCKED} for IHU unlocked
-     */
-    private IntentFilter getIntentFilter() {
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
-        filter.addAction(Intent.ACTION_USER_UNLOCKED);
-        return filter;
-    }
-
-    /**
-     * Show dialog for the given user
-     */
-    void showUnlockDialog(int uid, OnHideListener listener) {
-        showUnlockDialogAfterDelay(uid, 0, listener);
-    }
-
-    /**
-     * Show dialog for the given user after the certain time of delay has elapsed
-     *
-     * @param uid the user to unlock
-     * @param listener listener that listens to dialog hide
-     */
-    void showUnlockDialogAfterDelay(int uid, OnHideListener listener) {
-        long delayMillis = mResources.getInteger(R.integer.unlock_dialog_delay_ms);
-        showUnlockDialogAfterDelay(uid, delayMillis, listener);
-    }
-
-    /**
-     * Show dialog for the given user after the supplied delay has elapsed
-     */
-    private void showUnlockDialogAfterDelay(int uid, long delayMillis, OnHideListener listener) {
-        setUid(uid);
-        mOnHideListener = listener;
-        if (!mIsDialogShowing) {
-            logd("Receiver registered");
-            mContext.registerReceiverAsUser(this, UserHandle.ALL, mFilter,
-                    /* broadcastPermission= */ null,
-                    /* scheduler= */ null);
-            new Handler().postDelayed(() -> {
-                if (!mUserManager.isUserUnlocked(uid)) {
-                    logd("Showed unlock dialog for user: " + uid + " after " + delayMillis
-                            + " delay.");
-                    mWindowManager.addView(mUnlockDialogLayout, mParams);
-                }
-            }, delayMillis);
-        }
-        mIsDialogShowing = true;
-    }
-
-    private void setUid(int uid) {
-        mUid = uid;
-        TextView userName = mUnlockDialogLayout.findViewById(R.id.user_name);
-        userName.setText(mUserManager.getUserInfo(mUid).name);
-        ImageView avatar = mUnlockDialogLayout.findViewById(R.id.avatar);
-        avatar.setImageBitmap(mUserManager.getUserIcon(mUid));
-        setButtonText();
-    }
-
-    private void hideUnlockDialog(boolean dismissUserSwitcher) {
-        if (!mIsDialogShowing) {
-            return;
-        }
-        mWindowManager.removeView(mUnlockDialogLayout);
-        logd("Receiver unregistered");
-        mContext.unregisterReceiver(this);
-        if (mOnHideListener != null) {
-            mOnHideListener.onHide(dismissUserSwitcher);
-        }
-        mIsDialogShowing = false;
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        String action = intent.getAction();
-        if (action == null) {
-            return;
-        }
-        switch (action) {
-            case BluetoothAdapter.ACTION_BLE_STATE_CHANGED:
-                logd("Received ACTION_BLE_STATE_CHANGED");
-                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
-                if (state == BluetoothAdapter.STATE_BLE_ON) {
-                    logd("Received BLE_ON");
-                    mUnlockingText.setText(R.string.unlock_dialog_message_start);
-                }
-                break;
-            case Intent.ACTION_USER_UNLOCKED:
-                int uid = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                if (uid == mUid) {
-                    logd("IHU unlocked");
-                    hideUnlockDialog(/* notifyOnHideListener= */false);
-                } else {
-                    Log.e(TAG, "Received ACTION_USER_UNLOCKED for unexpected uid: " + uid);
-                }
-                break;
-            default:
-                Log.e(TAG, "Encountered unexpected action when attempting to set "
-                        + "unlock state message: " + action);
-        }
-    }
-
-    // Set button text based on screen lock type
-    private void setButtonText() {
-        LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
-        int passwordQuality = lockPatternUtils.getActivePasswordQuality(mUid);
-        switch (passwordQuality) {
-            // PIN
-            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
-            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
-                mButton.setText(R.string.unlock_dialog_button_text_pin);
-                break;
-            // Pattern
-            case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
-                mButton.setText(R.string.unlock_dialog_button_text_pattern);
-                break;
-            // Password
-            case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
-            case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
-            case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
-                mButton.setText(R.string.unlock_dialog_button_text_password);
-                break;
-            default:
-                Log.e(TAG, "Encountered unexpected screen lock type when attempting to set "
-                        + "button text:" + passwordQuality);
-        }
-    }
-
-    private WindowManager.LayoutParams createLayoutParams() {
-        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(
-                WindowManager.LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
-                WindowManager.LayoutParams.FLAG_FULLSCREEN
-                        | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
-                        | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
-                PixelFormat.TRANSLUCENT
-        );
-        attrs.setFitInsetsTypes(0 /* types */);
-        return attrs;
-    }
-
-    private void logd(String message) {
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, message);
-        }
-    }
-
-    /**
-     * Listener used to notify when the dialog is hidden
-     */
-    interface OnHideListener {
-        void onHide(boolean dismissUserSwitcher);
-    }
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
index 6277297..50e43be 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/FullscreenUserSwitcherViewMediator.java
@@ -16,23 +16,14 @@
 
 package com.android.systemui.car.userswitcher;
 
-import android.car.Car;
-import android.car.trust.CarTrustAgentEnrollmentManager;
-import android.car.userlib.CarUserManagerHelper;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.os.Handler;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
 
-import com.android.internal.widget.LockPatternUtils;
 import com.android.systemui.R;
-import com.android.systemui.car.CarServiceProvider;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -52,9 +43,6 @@
     private static final String TAG = FullscreenUserSwitcherViewMediator.class.getSimpleName();
 
     private final Context mContext;
-    private final UserManager mUserManager;
-    private final CarServiceProvider mCarServiceProvider;
-    private final CarTrustAgentUnlockDialogHelper mUnlockDialogHelper;
     private final CarStatusBarKeyguardViewManager mCarStatusBarKeyguardViewManager;
     private final Handler mMainHandler;
     private final StatusBarStateController mStatusBarStateController;
@@ -62,39 +50,12 @@
     private final ScreenLifecycle mScreenLifecycle;
     private final CarStatusBar mCarStatusBar;
     private final boolean mIsUserSwitcherEnabled;
-    private final CarUserManagerHelper mCarUserManagerHelper;
-
-    private CarTrustAgentEnrollmentManager mEnrollmentManager;
-    private UserGridRecyclerView.UserRecord mSelectedUser;
-    private final CarTrustAgentUnlockDialogHelper.OnHideListener mOnHideListener =
-            dismissUserSwitcher -> {
-                if (dismissUserSwitcher) {
-                    dismissUserSwitcher();
-                } else {
-                    // Re-draw the parent view, otherwise the unlock dialog will not be removed
-                    // from the screen immediately.
-                    invalidateFullscreenUserSwitcherView();
-                }
-            };
-    private final BroadcastReceiver mUserUnlockReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "user 0 is unlocked, SharedPreference is accessible.");
-            }
-            showDialogForInitialUser();
-            mContext.unregisterReceiver(mUserUnlockReceiver);
-        }
-    };
 
     @Inject
     public FullscreenUserSwitcherViewMediator(
             Context context,
             @Main Resources resources,
             @Main Handler mainHandler,
-            UserManager userManager,
-            CarServiceProvider carServiceProvider,
-            CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper,
             CarStatusBarKeyguardViewManager carStatusBarKeyguardViewManager,
             CarStatusBar carStatusBar,
             StatusBarStateController statusBarStateController,
@@ -105,21 +66,12 @@
         mIsUserSwitcherEnabled = resources.getBoolean(R.bool.config_enableFullscreenUserSwitcher);
 
         mMainHandler = mainHandler;
-        mUserManager = userManager;
 
-        mCarServiceProvider = carServiceProvider;
-        mCarServiceProvider.addListener(
-                car -> mEnrollmentManager = (CarTrustAgentEnrollmentManager) car.getCarManager(
-                        Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE));
-
-        mUnlockDialogHelper = carTrustAgentUnlockDialogHelper;
         mCarStatusBarKeyguardViewManager = carStatusBarKeyguardViewManager;
         mCarStatusBar = carStatusBar;
         mStatusBarStateController = statusBarStateController;
         mFullScreenUserSwitcherViewController = fullScreenUserSwitcherViewController;
         mScreenLifecycle = screenLifecycle;
-
-        mCarUserManagerHelper = new CarUserManagerHelper(mContext);
     }
 
     @Override
@@ -127,18 +79,6 @@
         registerUserSwitcherShowListeners();
         registerUserSwitcherHideListeners();
         registerHideKeyguardListeners();
-
-        if (mUserManager.isUserUnlocked(UserHandle.USER_SYSTEM)) {
-            // User0 is unlocked, switched to the initial user
-            showDialogForInitialUser();
-        } else {
-            // listen to USER_UNLOCKED
-            mContext.registerReceiverAsUser(mUserUnlockReceiver,
-                    UserHandle.getUserHandleForUid(UserHandle.USER_SYSTEM),
-                    new IntentFilter(Intent.ACTION_USER_UNLOCKED),
-                    /* broadcastPermission= */ null,
-                    /* scheduler= */ null);
-        }
     }
 
     private void registerUserSwitcherShowListeners() {
@@ -194,26 +134,16 @@
     }
 
     /**
-     * Every time user clicks on an item in the switcher, if the clicked user has no trusted
-     * device, we hide the switcher, either gradually or immediately.
-     * If the user has trusted device, we show an unlock dialog to notify user the unlock
-     * state.
-     * When the unlock dialog is dismissed by user, we hide the unlock dialog and the switcher.
-     * We dismiss the entire keyguard when we hide the switcher if user clicked on the
-     * foreground user (user we're already logged in as).
+     * Every time user clicks on an item in the switcher, we hide the switcher.
+     *
+     * We dismiss the entire keyguard if user clicked on the foreground user (user we're already
+     * logged in as).
      */
     private void onUserSelected(UserGridRecyclerView.UserRecord record) {
-        mSelectedUser = record;
-        if (record.mInfo != null) {
-            if (hasScreenLock(record.mInfo.id) && hasTrustedDevice(record.mInfo.id)) {
-                mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, mOnHideListener);
-                return;
-            }
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "no trusted device enrolled for uid: " + record.mInfo.id);
-            }
+        hide();
+        if (record.mType == UserGridRecyclerView.UserRecord.FOREGROUND_USER) {
+            mCarStatusBar.dismissKeyguard();
         }
-        dismissUserSwitcher();
     }
 
     // We automatically dismiss keyguard unless user switcher is being shown above the keyguard.
@@ -234,53 +164,6 @@
         }
     }
 
-    private boolean hasScreenLock(int uid) {
-        LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext);
-        return lockPatternUtils.getCredentialTypeForUser(uid)
-                != LockPatternUtils.CREDENTIAL_TYPE_NONE;
-    }
-
-    private boolean hasTrustedDevice(int uid) {
-        if (mEnrollmentManager == null) { // car service not ready, so it cannot be available.
-            return false;
-        }
-        return !mEnrollmentManager.getEnrolledDeviceInfoForUser(uid).isEmpty();
-    }
-
-    private void dismissUserSwitcher() {
-        if (mSelectedUser == null) {
-            Log.e(TAG, "Request to dismiss user switcher, but no user selected");
-            return;
-        }
-        if (mSelectedUser.mType == UserGridRecyclerView.UserRecord.FOREGROUND_USER) {
-            hide();
-            mCarStatusBar.dismissKeyguard();
-            return;
-        }
-        hide();
-    }
-
-    private void showDialogForInitialUser() {
-        int initialUser = mCarUserManagerHelper.getInitialUser();
-        UserInfo initialUserInfo = mUserManager.getUserInfo(initialUser);
-        mSelectedUser = new UserGridRecyclerView.UserRecord(initialUserInfo,
-                UserGridRecyclerView.UserRecord.FOREGROUND_USER);
-
-        // If the initial user has screen lock and trusted device, display the unlock dialog on the
-        // keyguard.
-        if (hasScreenLock(initialUser) && hasTrustedDevice(initialUser)) {
-            mUnlockDialogHelper.showUnlockDialogAfterDelay(initialUser,
-                    mOnHideListener);
-        } else {
-            // If no trusted device, dismiss the keyguard.
-            dismissUserSwitcher();
-        }
-    }
-
-    private void invalidateFullscreenUserSwitcherView() {
-        mFullScreenUserSwitcherViewController.invalidate();
-    }
-
     private void hide() {
         mFullScreenUserSwitcherViewController.stop();
     }
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayPanelViewController.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayPanelViewController.java
new file mode 100644
index 0000000..58022f1
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayPanelViewController.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.window;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import androidx.annotation.CallSuper;
+
+import com.android.systemui.R;
+import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+
+/**
+ * The {@link OverlayPanelViewController} provides additional dragging animation capabilities to
+ * {@link OverlayViewController}.
+ */
+public abstract class OverlayPanelViewController extends OverlayViewController {
+
+    private static final boolean DEBUG = true;
+    private static final String TAG = "OverlayPanelViewController";
+
+    // used to calculate how fast to open or close the window
+    protected static final float DEFAULT_FLING_VELOCITY = 0;
+    // max time a fling animation takes
+    protected static final float FLING_ANIMATION_MAX_TIME = 0.5f;
+    // acceleration rate for the fling animation
+    protected static final float FLING_SPEED_UP_FACTOR = 0.6f;
+
+    protected static final int SWIPE_DOWN_MIN_DISTANCE = 25;
+    protected static final int SWIPE_MAX_OFF_PATH = 75;
+    protected static final int SWIPE_THRESHOLD_VELOCITY = 200;
+
+    private final FlingAnimationUtils mFlingAnimationUtils;
+    private final CarDeviceProvisionedController mCarDeviceProvisionedController;
+    private final View.OnTouchListener mDragOpenTouchListener;
+    private final View.OnTouchListener mDragCloseTouchListener;
+
+    private final int mSettleClosePercentage;
+    private int mPercentageFromBottom;
+
+    private boolean mPanelVisible;
+    private boolean mPanelExpanded;
+
+    private float mOpeningVelocity = DEFAULT_FLING_VELOCITY;
+    private float mClosingVelocity = DEFAULT_FLING_VELOCITY;
+
+    private boolean mIsAnimating;
+    private boolean mIsTracking;
+
+    public OverlayPanelViewController(
+            Context context,
+            @Main Resources resources,
+            int stubId,
+            OverlayViewGlobalStateController overlayViewGlobalStateController,
+            FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
+            CarDeviceProvisionedController carDeviceProvisionedController
+    ) {
+        super(stubId, overlayViewGlobalStateController);
+
+        mFlingAnimationUtils = flingAnimationUtilsBuilder
+                .setMaxLengthSeconds(FLING_ANIMATION_MAX_TIME)
+                .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
+                .build();
+        mCarDeviceProvisionedController = carDeviceProvisionedController;
+
+        mSettleClosePercentage = resources.getInteger(
+                R.integer.notification_settle_close_percentage);
+
+        // Attached to the top navigation bar (i.e. status bar) to detect pull down of the
+        // notification shade.
+        GestureDetector openGestureDetector = new GestureDetector(context,
+                new OpenGestureListener() {
+                    @Override
+                    protected void open() {
+                        animateExpandPanel();
+                    }
+                });
+
+        // Attached to the NavBars to close the notification shade
+        GestureDetector navBarCloseNotificationGestureDetector = new GestureDetector(context,
+                new SystemBarCloseGestureListener() {
+                    @Override
+                    protected void close() {
+                        if (isPanelExpanded()) {
+                            animateCollapsePanel();
+                        }
+                    }
+                });
+
+        mDragOpenTouchListener = (v, event) -> {
+            if (!mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
+                return true;
+            }
+            if (!isInflated()) {
+                getOverlayViewGlobalStateController().inflateView(this);
+            }
+
+            boolean consumed = openGestureDetector.onTouchEvent(event);
+            if (consumed) {
+                return true;
+            }
+            maybeCompleteAnimation(event);
+            return true;
+        };
+
+        mDragCloseTouchListener = (v, event) -> {
+            if (!isInflated()) {
+                return true;
+            }
+            boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
+            if (consumed) {
+                return true;
+            }
+            maybeCompleteAnimation(event);
+            return true;
+        };
+    }
+
+    /** Toggles the visibility of the panel. */
+    public void toggle() {
+        if (!isInflated()) {
+            getOverlayViewGlobalStateController().inflateView(this);
+        }
+        if (isPanelExpanded()) {
+            animateCollapsePanel();
+        } else {
+            animateExpandPanel();
+        }
+    }
+
+    /* ***************************************************************************************** *
+     * Panel Animation
+     * ***************************************************************************************** */
+
+    /** Animates the closing of the panel. */
+    protected void animateCollapsePanel() {
+        if (!shouldAnimateCollapsePanel()) {
+            return;
+        }
+
+        if (!isPanelExpanded() || !isPanelVisible()) {
+            return;
+        }
+
+        onAnimateCollapsePanel();
+        getOverlayViewGlobalStateController().setWindowFocusable(false);
+        animatePanel(mClosingVelocity, /* isClosing= */ true);
+    }
+
+    /** Determines whether {@link #animateCollapsePanel()} should collapse the panel. */
+    protected abstract boolean shouldAnimateCollapsePanel();
+
+    /** Called when the panel is beginning to collapse. */
+    protected abstract void onAnimateCollapsePanel();
+
+    /** Animates the expansion of the panel. */
+    protected void animateExpandPanel() {
+        if (!shouldAnimateExpandPanel()) {
+            return;
+        }
+
+        if (!mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
+            return;
+        }
+
+        onAnimateExpandPanel();
+        setPanelVisible(true);
+        animatePanel(mOpeningVelocity, /* isClosing= */ false);
+
+        setPanelExpanded(true);
+    }
+
+    /** Determines whether {@link #animateExpandPanel()}} should expand the panel. */
+    protected abstract boolean shouldAnimateExpandPanel();
+
+    /** Called when the panel is beginning to expand. */
+    protected abstract void onAnimateExpandPanel();
+
+    /**
+     * Depending on certain conditions, determines whether to fully expand or collapse the panel.
+     */
+    protected void maybeCompleteAnimation(MotionEvent event) {
+        if (event.getActionMasked() == MotionEvent.ACTION_UP
+                && isPanelVisible()) {
+            if (mSettleClosePercentage < mPercentageFromBottom) {
+                animatePanel(DEFAULT_FLING_VELOCITY, false);
+            } else {
+                animatePanel(DEFAULT_FLING_VELOCITY, true);
+            }
+        }
+    }
+
+    /**
+     * Animates the panel from one position to other. This is used to either open or
+     * close the panel completely with a velocity. If the animation is to close the
+     * panel this method also makes the view invisible after animation ends.
+     */
+    protected void animatePanel(float velocity, boolean isClosing) {
+        float to = 0;
+        if (!isClosing) {
+            to = getLayout().getHeight();
+        }
+
+        Rect rect = getLayout().getClipBounds();
+        if (rect != null && rect.bottom != to) {
+            float from = rect.bottom;
+            animate(from, to, velocity, isClosing);
+            return;
+        }
+
+        // We will only be here if the shade is being opened programmatically or via button when
+        // height of the layout was not calculated.
+        ViewTreeObserver notificationTreeObserver = getLayout().getViewTreeObserver();
+        notificationTreeObserver.addOnGlobalLayoutListener(
+                new ViewTreeObserver.OnGlobalLayoutListener() {
+                    @Override
+                    public void onGlobalLayout() {
+                        ViewTreeObserver obs = getLayout().getViewTreeObserver();
+                        obs.removeOnGlobalLayoutListener(this);
+                        float to = getLayout().getHeight();
+                        animate(/* from= */ 0, to, velocity, isClosing);
+                    }
+                });
+    }
+
+    private void animate(float from, float to, float velocity, boolean isClosing) {
+        if (mIsAnimating) {
+            return;
+        }
+        mIsAnimating = true;
+        mIsTracking = true;
+        ValueAnimator animator = ValueAnimator.ofFloat(from, to);
+        animator.addUpdateListener(
+                animation -> {
+                    float animatedValue = (Float) animation.getAnimatedValue();
+                    setViewClipBounds((int) animatedValue);
+                });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                mIsAnimating = false;
+                mIsTracking = false;
+                mOpeningVelocity = DEFAULT_FLING_VELOCITY;
+                mClosingVelocity = DEFAULT_FLING_VELOCITY;
+                if (isClosing) {
+                    setPanelVisible(false);
+                    getLayout().setClipBounds(null);
+                    onCollapseAnimationEnd();
+                    setPanelExpanded(false);
+                } else {
+                    onExpandAnimationEnd();
+                    setPanelExpanded(true);
+                }
+            }
+        });
+        getFlingAnimationUtils().apply(animator, from, to, Math.abs(velocity));
+        animator.start();
+    }
+
+    /**
+     * Called in {@link Animator.AnimatorListener#onAnimationEnd(Animator)} when the panel is
+     * closing.
+     */
+    protected abstract void onCollapseAnimationEnd();
+
+    /**
+     * Called in {@link Animator.AnimatorListener#onAnimationEnd(Animator)} when the panel is
+     * opening.
+     */
+    protected abstract void onExpandAnimationEnd();
+
+    /* ***************************************************************************************** *
+     * Panel Visibility
+     * ***************************************************************************************** */
+
+    /** Set the panel view to be visible. */
+    protected final void setPanelVisible(boolean visible) {
+        mPanelVisible = visible;
+        onPanelVisible(visible);
+    }
+
+    /** Returns {@code true} if panel is visible. */
+    public final boolean isPanelVisible() {
+        return mPanelVisible;
+    }
+
+    /** Business logic run when panel visibility is set. */
+    @CallSuper
+    protected void onPanelVisible(boolean visible) {
+        if (DEBUG) {
+            Log.e(TAG, "onPanelVisible: " + visible);
+        }
+
+        if (visible && !getOverlayViewGlobalStateController().isWindowVisible()) {
+            getOverlayViewGlobalStateController().setWindowVisible(true);
+        }
+        if (!visible && getOverlayViewGlobalStateController().isWindowVisible()) {
+            getOverlayViewGlobalStateController().setWindowVisible(false);
+        }
+        getLayout().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+        getOverlayViewGlobalStateController().setWindowFocusable(visible);
+    }
+
+    /* ***************************************************************************************** *
+     * Panel Expansion
+     * ***************************************************************************************** */
+
+    /**
+     * Set the panel state to expanded. This will expand or collapse the overlay window if
+     * necessary.
+     */
+    protected final void setPanelExpanded(boolean expand) {
+        mPanelExpanded = expand;
+        onPanelExpanded(expand);
+    }
+
+    /** Returns {@code true} if panel is expanded. */
+    public final boolean isPanelExpanded() {
+        return mPanelExpanded;
+    }
+
+    @CallSuper
+    protected void onPanelExpanded(boolean expand) {
+        if (DEBUG) {
+            Log.e(TAG, "onPanelExpanded: " + expand);
+        }
+    }
+
+    /* ***************************************************************************************** *
+     * Misc
+     * ***************************************************************************************** */
+
+    protected void calculatePercentageFromBottom(float height) {
+        if (getLayout().getHeight() > 0) {
+            mPercentageFromBottom = (int) Math.abs(
+                    height / getLayout().getHeight() * 100);
+        }
+    }
+
+    protected void setViewClipBounds(int height) {
+        if (height > getLayout().getHeight()) {
+            height = getLayout().getHeight();
+        }
+        Rect clipBounds = new Rect();
+        clipBounds.set(0, 0, getLayout().getWidth(), height);
+        getLayout().setClipBounds(clipBounds);
+        onScroll(height);
+    }
+
+    /** Called while scrolling. */
+    protected abstract void onScroll(int height);
+
+    /* ***************************************************************************************** *
+     * Getters
+     * ***************************************************************************************** */
+
+    /** Returns the open touch listener. */
+    public final View.OnTouchListener getDragOpenTouchListener() {
+        return mDragOpenTouchListener;
+    }
+
+    /** Returns the close touch listener. */
+    public final View.OnTouchListener getDragCloseTouchListener() {
+        return mDragCloseTouchListener;
+    }
+
+    /** Gets the fling animation utils used for animating this panel. */
+    protected final FlingAnimationUtils getFlingAnimationUtils() {
+        return mFlingAnimationUtils;
+    }
+
+    /** Returns {@code true} if the panel is currently tracking. */
+    protected final boolean isTracking() {
+        return mIsTracking;
+    }
+
+    /** Returns {@code true} if the panel is currently animating. */
+    protected final boolean isAnimating() {
+        return mIsAnimating;
+    }
+
+    /** Returns the percentage of the panel that is open from the bottom. */
+    protected final int getPercentageFromBottom() {
+        return mPercentageFromBottom;
+    }
+
+    /** Returns the percentage at which we've determined whether to open or close the panel. */
+    protected final int getSettleClosePercentage() {
+        return mSettleClosePercentage;
+    }
+
+    /* ***************************************************************************************** *
+     * Gesture Listeners
+     * ***************************************************************************************** */
+
+    /** Called when the user is beginning to scroll down the panel. */
+    protected abstract void onOpenScrollStart();
+
+    /**
+     * Only responsible for open hooks. Since once the panel opens it covers all elements
+     * there is no need to merge with close.
+     */
+    protected abstract class OpenGestureListener extends
+            GestureDetector.SimpleOnGestureListener {
+
+        @Override
+        public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
+                float distanceY) {
+
+            if (!isPanelVisible()) {
+                onOpenScrollStart();
+            }
+            setPanelVisible(true);
+
+            // clips the view for the notification shade when the user scrolls to open.
+            setViewClipBounds((int) event2.getRawY());
+
+            // Initially the scroll starts with height being zero. This checks protects from divide
+            // by zero error.
+            calculatePercentageFromBottom(event2.getRawY());
+
+            mIsTracking = true;
+            return true;
+        }
+
+
+        @Override
+        public boolean onFling(MotionEvent event1, MotionEvent event2,
+                float velocityX, float velocityY) {
+            if (velocityY > SWIPE_THRESHOLD_VELOCITY) {
+                mOpeningVelocity = velocityY;
+                open();
+                return true;
+            }
+            animatePanel(DEFAULT_FLING_VELOCITY, true);
+
+            return false;
+        }
+
+        protected abstract void open();
+    }
+
+    /** Determines whether the scroll event should allow closing of the panel. */
+    protected abstract boolean shouldAllowClosingScroll();
+
+    protected abstract class CloseGestureListener extends
+            GestureDetector.SimpleOnGestureListener {
+
+        @Override
+        public boolean onSingleTapUp(MotionEvent motionEvent) {
+            if (isPanelExpanded()) {
+                animatePanel(DEFAULT_FLING_VELOCITY, true);
+            }
+            return true;
+        }
+
+        @Override
+        public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
+                float distanceY) {
+            // should not clip while scroll to the bottom of the list.
+            if (!shouldAllowClosingScroll()) {
+                return false;
+            }
+            float actualNotificationHeight =
+                    getLayout().getHeight() - (event1.getRawY() - event2.getRawY());
+            if (actualNotificationHeight > getLayout().getHeight()) {
+                actualNotificationHeight = getLayout().getHeight();
+            }
+            if (getLayout().getHeight() > 0) {
+                mPercentageFromBottom = (int) Math.abs(
+                        actualNotificationHeight / getLayout().getHeight() * 100);
+                boolean isUp = distanceY > 0;
+
+                // This check is to figure out if onScroll was called while swiping the card at
+                // bottom of the list. At that time we should not allow notification shade to
+                // close. We are also checking for the upwards swipe gesture here because it is
+                // possible if a user is closing the notification shade and while swiping starts
+                // to open again but does not fling. At that time we should allow the
+                // notification shade to close fully or else it would stuck in between.
+                if (Math.abs(getLayout().getHeight() - actualNotificationHeight)
+                        > SWIPE_DOWN_MIN_DISTANCE && isUp) {
+                    setViewClipBounds((int) actualNotificationHeight);
+                    mIsTracking = true;
+                } else if (!isUp) {
+                    setViewClipBounds((int) actualNotificationHeight);
+                }
+            }
+            // if we return true the items in RV won't be scrollable.
+            return false;
+        }
+
+
+        @Override
+        public boolean onFling(MotionEvent event1, MotionEvent event2,
+                float velocityX, float velocityY) {
+            // should not fling if the touch does not start when view is at the bottom of the list.
+            if (!shouldAllowClosingScroll()) {
+                return false;
+            }
+            if (Math.abs(event1.getX() - event2.getX()) > SWIPE_MAX_OFF_PATH
+                    || Math.abs(velocityY) < SWIPE_THRESHOLD_VELOCITY) {
+                // swipe was not vertical or was not fast enough
+                return false;
+            }
+            boolean isUp = velocityY < 0;
+            if (isUp) {
+                close();
+                return true;
+            } else {
+                // we should close the shade
+                animatePanel(velocityY, false);
+            }
+            return false;
+        }
+
+        protected abstract void close();
+    }
+
+    protected abstract class SystemBarCloseGestureListener extends CloseGestureListener {
+        @Override
+        public boolean onSingleTapUp(MotionEvent e) {
+            mClosingVelocity = DEFAULT_FLING_VELOCITY;
+            if (isPanelExpanded()) {
+                close();
+            }
+            return super.onSingleTapUp(e);
+        }
+
+        @Override
+        public boolean onScroll(MotionEvent event1, MotionEvent event2, float distanceX,
+                float distanceY) {
+            calculatePercentageFromBottom(event2.getRawY());
+            setViewClipBounds((int) event2.getRawY());
+            return true;
+        }
+    }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
index f2748b8..76557fd 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
@@ -20,13 +20,14 @@
 import static org.mockito.Mockito.when;
 
 import android.os.Handler;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableResources;
 import android.view.LayoutInflater;
 import android.view.WindowManager;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.car.CarDeviceProvisionedController;
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayPanelViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayPanelViewControllerTest.java
new file mode 100644
index 0000000..04f2d06
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayPanelViewControllerTest.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.window;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.tests.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class OverlayPanelViewControllerTest extends SysuiTestCase {
+    private TestOverlayPanelViewController mOverlayPanelViewController;
+    private ViewGroup mBaseLayout;
+
+    @Mock
+    private OverlayViewGlobalStateController mOverlayViewGlobalStateController;
+    @Mock
+    private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
+    @Mock
+    private FlingAnimationUtils mFlingAnimationUtils;
+    @Mock
+    private CarDeviceProvisionedController mCarDeviceProvisionedController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate(
+                R.layout.overlay_view_controller_test, /* root= */ null);
+
+        when(mFlingAnimationUtilsBuilder.setMaxLengthSeconds(anyFloat())).thenReturn(
+                mFlingAnimationUtilsBuilder);
+        when(mFlingAnimationUtilsBuilder.setSpeedUpFactor(anyFloat())).thenReturn(
+                mFlingAnimationUtilsBuilder);
+        when(mFlingAnimationUtilsBuilder.build()).thenReturn(mFlingAnimationUtils);
+        mOverlayPanelViewController = new TestOverlayPanelViewController(
+                getContext(),
+                getContext().getOrCreateTestableResources().getResources(),
+                R.id.overlay_view_controller_stub,
+                mOverlayViewGlobalStateController,
+                mFlingAnimationUtilsBuilder,
+                mCarDeviceProvisionedController);
+    }
+
+    @Test
+    public void toggle_notInflated_inflates() {
+        assertThat(mOverlayPanelViewController.isInflated()).isFalse();
+
+        mOverlayPanelViewController.toggle();
+
+        verify(mOverlayViewGlobalStateController).inflateView(mOverlayPanelViewController);
+    }
+
+    @Test
+    public void toggle_inflated_doesNotInflate() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        assertThat(mOverlayPanelViewController.isInflated()).isTrue();
+
+        mOverlayPanelViewController.toggle();
+
+        verify(mOverlayViewGlobalStateController, never()).inflateView(mOverlayPanelViewController);
+    }
+
+    @Test
+    public void toggle_notExpanded_panelExpands() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.setPanelExpanded(false);
+
+        mOverlayPanelViewController.toggle();
+
+        assertThat(mOverlayPanelViewController.mAnimateExpandPanelCalled).isTrue();
+    }
+
+    @Test
+    public void toggle_expanded_panelCollapses() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.setPanelExpanded(true);
+
+        mOverlayPanelViewController.toggle();
+
+        assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue();
+    }
+
+    @Test
+    public void animateCollapsePanel_shouldNotAnimateCollapsePanel_doesNotCollapse() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.setShouldAnimateCollapsePanel(false);
+
+        mOverlayPanelViewController.animateCollapsePanel();
+
+        assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue();
+        assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isFalse();
+    }
+
+    @Test
+    public void animateCollapsePanel_isNotExpanded_doesNotCollapse() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
+        mOverlayPanelViewController.setPanelExpanded(false);
+
+        mOverlayPanelViewController.animateCollapsePanel();
+
+        assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue();
+        assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isFalse();
+    }
+
+    @Test
+    public void animateCollapsePanel_isNotVisible_doesNotCollapse() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
+        mOverlayPanelViewController.setPanelExpanded(true);
+        mOverlayPanelViewController.setPanelVisible(false);
+
+        mOverlayPanelViewController.animateCollapsePanel();
+
+        assertThat(mOverlayPanelViewController.mAnimateCollapsePanelCalled).isTrue();
+        assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isFalse();
+    }
+
+    @Test
+    public void animateCollapsePanel_collapses() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
+        mOverlayPanelViewController.setPanelExpanded(true);
+        mOverlayPanelViewController.setPanelVisible(true);
+
+        mOverlayPanelViewController.animateCollapsePanel();
+
+        assertThat(mOverlayPanelViewController.mOnAnimateCollapsePanelCalled).isTrue();
+    }
+
+    @Test
+    public void animateCollapsePanel_removesWindowFocus() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.setShouldAnimateCollapsePanel(true);
+        mOverlayPanelViewController.setPanelExpanded(true);
+        mOverlayPanelViewController.setPanelVisible(true);
+
+        mOverlayPanelViewController.animateCollapsePanel();
+
+        verify(mOverlayViewGlobalStateController).setWindowFocusable(false);
+    }
+
+    @Test
+    public void animateExpandPanel_shouldNotAnimateExpandPanel_doesNotExpand() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.setShouldAnimateExpandPanel(false);
+
+        mOverlayPanelViewController.animateExpandPanel();
+
+        assertThat(mOverlayPanelViewController.mAnimateExpandPanelCalled).isTrue();
+        assertThat(mOverlayPanelViewController.mOnAnimateExpandPanelCalled).isFalse();
+    }
+
+    @Test
+    public void animateExpandPanel_userNotSetup_doesNotExpand() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
+        when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(false);
+
+        mOverlayPanelViewController.animateExpandPanel();
+
+        assertThat(mOverlayPanelViewController.mAnimateExpandPanelCalled).isTrue();
+        assertThat(mOverlayPanelViewController.mOnAnimateExpandPanelCalled).isFalse();
+    }
+
+    @Test
+    public void animateExpandPanel_expands() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
+        when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
+
+        mOverlayPanelViewController.animateExpandPanel();
+
+        assertThat(mOverlayPanelViewController.mOnAnimateExpandPanelCalled).isTrue();
+    }
+
+    @Test
+    public void animateExpandPanel_setsPanelVisible() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
+        when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
+
+        mOverlayPanelViewController.animateExpandPanel();
+
+        assertThat(mOverlayPanelViewController.isPanelVisible()).isTrue();
+    }
+
+    @Test
+    public void animateExpandPanel_setsPanelExpanded() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.setShouldAnimateExpandPanel(true);
+        when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
+
+        mOverlayPanelViewController.animateExpandPanel();
+
+        assertThat(mOverlayPanelViewController.isPanelExpanded()).isTrue();
+    }
+
+    @Test
+    public void setPanelVisible_setTrue_windowNotVisible_setsWindowVisible() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(false);
+
+        mOverlayPanelViewController.setPanelVisible(true);
+
+        verify(mOverlayViewGlobalStateController).setWindowVisible(true);
+    }
+
+    @Test
+    public void setPanelVisible_setTrue_windowVisible_doesNotSetWindowVisible() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(true);
+
+        mOverlayPanelViewController.setPanelVisible(true);
+
+        verify(mOverlayViewGlobalStateController, never()).setWindowVisible(true);
+    }
+
+    @Test
+    public void setPanelVisible_setTrue_setLayoutVisible() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.getLayout().setVisibility(View.INVISIBLE);
+
+        mOverlayPanelViewController.setPanelVisible(true);
+
+        assertThat(mOverlayPanelViewController.getLayout().getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void setPanelVisible_setTrue_setWindowFocusable() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.setPanelVisible(true);
+
+        verify(mOverlayViewGlobalStateController).setWindowFocusable(true);
+    }
+
+    @Test
+    public void setPanelVisible_setFalse_windowVisible_setsWindowNotVisible() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(true);
+
+        mOverlayPanelViewController.setPanelVisible(false);
+
+        verify(mOverlayViewGlobalStateController).setWindowVisible(false);
+    }
+
+    @Test
+    public void setPanelVisible_setFalse_windowNotVisible_doesNotSetWindowNotVisible() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        when(mOverlayViewGlobalStateController.isWindowVisible()).thenReturn(false);
+
+        mOverlayPanelViewController.setPanelVisible(false);
+
+        verify(mOverlayViewGlobalStateController, never()).setWindowVisible(false);
+    }
+
+    @Test
+    public void setPanelVisible_setFalse_setLayoutInvisible() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+        mOverlayPanelViewController.getLayout().setVisibility(View.VISIBLE);
+
+        mOverlayPanelViewController.setPanelVisible(false);
+
+        assertThat(mOverlayPanelViewController.getLayout().getVisibility()).isEqualTo(
+                View.INVISIBLE);
+    }
+
+    @Test
+    public void setPanelVisible_setFalse_setWindowNotFocusable() {
+        mOverlayPanelViewController.inflate(mBaseLayout);
+
+        mOverlayPanelViewController.setPanelVisible(false);
+
+        verify(mOverlayViewGlobalStateController).setWindowFocusable(false);
+    }
+
+    @Test
+    public void dragOpenTouchListener_isNotInflated_inflatesView() {
+        when(mCarDeviceProvisionedController.isCurrentUserFullySetup()).thenReturn(true);
+        assertThat(mOverlayPanelViewController.isInflated()).isFalse();
+
+        mOverlayPanelViewController.getDragOpenTouchListener().onTouch(/* v= */ null,
+                MotionEvent.obtain(/* downTime= */ 200, /* eventTime= */ 300,
+                        MotionEvent.ACTION_MOVE, /* x= */ 0, /* y= */ 0, /* metaState= */ 0));
+
+        verify(mOverlayViewGlobalStateController).inflateView(mOverlayPanelViewController);
+    }
+
+    private static class TestOverlayPanelViewController extends OverlayPanelViewController {
+
+        private boolean mShouldAnimateCollapsePanel;
+        private boolean mShouldAnimateExpandPanel;
+        private boolean mShouldAllowClosingScroll;
+
+        boolean mOnAnimateCollapsePanelCalled;
+        boolean mAnimateCollapsePanelCalled;
+        boolean mOnAnimateExpandPanelCalled;
+        boolean mAnimateExpandPanelCalled;
+        boolean mOnCollapseAnimationEndCalled;
+        boolean mOnExpandAnimationEndCalled;
+        boolean mOnOpenScrollStartEnd;
+        List<Integer> mOnScrollHeights;
+
+        TestOverlayPanelViewController(
+                Context context,
+                Resources resources,
+                int stubId,
+                OverlayViewGlobalStateController overlayViewGlobalStateController,
+                FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
+                CarDeviceProvisionedController carDeviceProvisionedController) {
+            super(context, resources, stubId, overlayViewGlobalStateController,
+                    flingAnimationUtilsBuilder,
+                    carDeviceProvisionedController);
+
+            mOnScrollHeights = new ArrayList<>();
+        }
+
+        public void setShouldAnimateCollapsePanel(boolean shouldAnimate) {
+            mShouldAnimateCollapsePanel = shouldAnimate;
+        }
+
+        @Override
+        protected boolean shouldAnimateCollapsePanel() {
+            return mShouldAnimateCollapsePanel;
+        }
+
+        @Override
+        protected void animateCollapsePanel() {
+            super.animateCollapsePanel();
+            mAnimateCollapsePanelCalled = true;
+        }
+
+        @Override
+        protected void onAnimateCollapsePanel() {
+            mOnAnimateCollapsePanelCalled = true;
+        }
+
+        public void setShouldAnimateExpandPanel(boolean shouldAnimate) {
+            mShouldAnimateExpandPanel = shouldAnimate;
+        }
+
+        @Override
+        protected boolean shouldAnimateExpandPanel() {
+            return mShouldAnimateExpandPanel;
+        }
+
+        @Override
+        protected void animateExpandPanel() {
+            super.animateExpandPanel();
+            mAnimateExpandPanelCalled = true;
+        }
+
+        @Override
+        protected void onAnimateExpandPanel() {
+            mOnAnimateExpandPanelCalled = true;
+        }
+
+        @Override
+        protected void onCollapseAnimationEnd() {
+            mOnCollapseAnimationEndCalled = true;
+        }
+
+        @Override
+        protected void onExpandAnimationEnd() {
+            mOnExpandAnimationEndCalled = true;
+        }
+
+        @Override
+        protected void onScroll(int height) {
+            mOnScrollHeights.add(height);
+        }
+
+        @Override
+        protected void onOpenScrollStart() {
+            mOnOpenScrollStartEnd = true;
+        }
+
+        public void setShouldAllowClosingScroll(boolean shouldAllow) {
+            mShouldAllowClosingScroll = shouldAllow;
+        }
+
+        @Override
+        protected boolean shouldAllowClosingScroll() {
+            return mShouldAllowClosingScroll;
+        }
+    }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java
index 3be9626..3313261 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/window/OverlayViewControllerTest.java
@@ -43,7 +43,7 @@
 @TestableLooper.RunWithLooper
 @SmallTest
 public class OverlayViewControllerTest extends SysuiTestCase {
-    private MockOverlayViewController mOverlayViewController;
+    private TestOverlayViewController mOverlayViewController;
     private ViewGroup mBaseLayout;
 
     @Mock
@@ -56,7 +56,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(/* testClass= */ this);
 
-        mOverlayViewController = new MockOverlayViewController(R.id.overlay_view_controller_stub,
+        mOverlayViewController = new TestOverlayViewController(R.id.overlay_view_controller_stub,
                 mOverlayViewGlobalStateController);
 
         mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate(
@@ -130,12 +130,12 @@
         assertThat(mOverlayViewController.mHideInternalCalled).isFalse();
     }
 
-    private static class MockOverlayViewController extends OverlayViewController {
+    private static class TestOverlayViewController extends OverlayViewController {
         boolean mOnFinishInflateCalled = false;
         boolean mShowInternalCalled = false;
         boolean mHideInternalCalled = false;
 
-        MockOverlayViewController(int stubId,
+        TestOverlayViewController(int stubId,
                 OverlayViewGlobalStateController overlayViewGlobalStateController) {
             super(stubId, overlayViewGlobalStateController);
         }
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 53e7921..1007d83 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -646,6 +646,8 @@
     <string name="wifi_verbose_logging">Enable Wi\u2011Fi Verbose Logging</string>
     <!-- Setting Checkbox title whether to disable WiFi Scan Throttling. [CHAR LIMIT=40] -->
     <string name="wifi_scan_throttling">Wi\u2011Fi scan throttling</string>
+    <!-- Setting Checkbox title whether to enable WiFi enhanced mac randomization. [CHAR LIMIT=40] -->
+    <string name="wifi_enhanced_mac_randomization">Wi\u2011Fi\u2011enhanced MAC randomization</string>
     <!-- Setting Checkbox title whether to always keep mobile data active. [CHAR LIMIT=80] -->
     <string name="mobile_data_always_on">Mobile data always active</string>
     <!-- Setting Checkbox title whether to enable hardware acceleration for tethering. [CHAR LIMIT=80] -->
@@ -716,6 +718,8 @@
     <string name="wifi_verbose_logging_summary">Increase Wi\u2011Fi logging level, show per SSID RSSI in Wi\u2011Fi Picker</string>
     <!-- Setting Checkbox summary whether to disable Wifi scan throttling [CHAR LIMIT=NONE] -->
     <string name="wifi_scan_throttling_summary">Reduces battery drain &amp; improves network performance</string>
+    <!-- Setting Checkbox title whether to enable WiFi enhanced mac randomization. [CHAR LIMIT=NONE] -->
+    <string name="wifi_enhanced_mac_randomization_summary">This toggle affects MAC randomization behavior for client mode only.\nWhen this mode is activated, any networks that have MAC randomization enabled may have their MAC addresses re\u2011randomized during association, depending on when the client last disconnected from the network. Re\u2011randomization does not occur if the device reconnects in 4 hours or less.</string>
     <!-- Label indicating network has been manually marked as metered -->
     <string name="wifi_metered_label">Metered</string>
     <!-- Label indicating network has been manually marked as unmetered -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
index ff40d8e..450bdb1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
@@ -202,6 +202,12 @@
     }
 
     /**
+     * Gives descendants a chance to log Preference click event
+     */
+    protected void logPreferenceClick(Intent intent) {
+    }
+
+    /**
      * Returns the settings parsed from the attributes of the
      * {@link SettingInjectorService#META_DATA_NAME} tag, or null.
      *
@@ -315,6 +321,7 @@
             // Settings > Location.
             Intent settingIntent = new Intent();
             settingIntent.setClassName(mInfo.packageName, mInfo.settingsActivity);
+            logPreferenceClick(settingIntent);
             // Sometimes the user may navigate back to "Settings" and launch another different
             // injected setting after one injected setting has been launched.
             //
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index 9d7e2c8..b1234f2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -18,11 +18,15 @@
 
 import android.content.Context;
 import android.net.NetworkTemplate;
+import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
 import com.android.internal.util.ArrayUtils;
+
+import java.util.List;
+
 /**
  * Utils class for data usage
  */
@@ -33,26 +37,42 @@
      * Return mobile NetworkTemplate based on {@code subId}
      */
     public static NetworkTemplate getMobileTemplate(Context context, int subId) {
-        final TelephonyManager telephonyManager = context.getSystemService(
-                TelephonyManager.class);
-        final SubscriptionManager subscriptionManager = context.getSystemService(
-                SubscriptionManager.class);
-        final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
-                telephonyManager.getSubscriberId());
+        final TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+        final int mobileDefaultSubId = telephonyManager.getSubscriptionId();
 
-        if (!subscriptionManager.isActiveSubscriptionId(subId)) {
-            Log.i(TAG, "Subscription is not active: " + subId);
-            return mobileAll;
+        final SubscriptionManager subscriptionManager =
+                context.getSystemService(SubscriptionManager.class);
+        final List<SubscriptionInfo> subInfoList =
+                subscriptionManager.getAvailableSubscriptionInfoList();
+        if (subInfoList == null) {
+            Log.i(TAG, "Subscription is not inited: " + subId);
+            return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId);
         }
 
-        final String[] mergedSubscriberIds = telephonyManager.createForSubscriptionId(subId)
-                .getMergedImsisFromGroup();
+        for (SubscriptionInfo subInfo : subInfoList) {
+            if ((subInfo != null) && (subInfo.getSubscriptionId() == subId)) {
+                return getNormalizedMobileTemplate(telephonyManager, subId);
+            }
+        }
+        Log.i(TAG, "Subscription is not active: " + subId);
+        return getMobileTemplateForSubId(telephonyManager, mobileDefaultSubId);
+    }
 
+    private static NetworkTemplate getNormalizedMobileTemplate(
+            TelephonyManager telephonyManager, int subId) {
+        final NetworkTemplate mobileTemplate = getMobileTemplateForSubId(telephonyManager, subId);
+        final String[] mergedSubscriberIds = telephonyManager
+                .createForSubscriptionId(subId).getMergedImsisFromGroup();
         if (ArrayUtils.isEmpty(mergedSubscriberIds)) {
             Log.i(TAG, "mergedSubscriberIds is null.");
-            return mobileAll;
+            return mobileTemplate;
         }
 
-        return NetworkTemplate.normalize(mobileAll, mergedSubscriberIds);
+        return NetworkTemplate.normalize(mobileTemplate, mergedSubscriberIds);
+    }
+
+    private static NetworkTemplate getMobileTemplateForSubId(
+            TelephonyManager telephonyManager, int subId) {
+        return NetworkTemplate.buildTemplateMobileAll(telephonyManager.getSubscriberId(subId));
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
index 8aa0aec..a53bc9f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
@@ -102,12 +102,11 @@
         // Turn off divider
         view.findViewById(R.id.two_target_divider).setVisibility(View.INVISIBLE);
 
-        // Enable the icon button when this Entry is a canManageSubscription entry.
+        // Enable the icon button when the help string in this WifiEntry is not null.
         final ImageButton imageButton = (ImageButton) view.findViewById(R.id.icon_button);
         final ImageView frictionImageView = (ImageView) view.findViewById(
                 R.id.friction_icon);
-        if (mWifiEntry.canManageSubscription() && !mWifiEntry.isSaved()
-                && !mWifiEntry.isSubscription()
+        if (mWifiEntry.getHelpUriString() != null
                 && mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) {
             final Drawable drawablehelp = getDrawable(R.drawable.ic_help);
             drawablehelp.setTintList(
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
index a9f31ce..46e699d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
@@ -64,7 +64,7 @@
 
     private static final String MOCK_TITLE = "title";
     private static final String MOCK_SUMMARY = "summary";
-
+    private static final String FAKE_URI_STRING = "fakeuri";
 
     @Before
     public void setUp() {
@@ -155,22 +155,23 @@
     }
 
     @Test
-    public void canManageSubscription_shouldSetImageButtonVisible() {
-        when(mMockWifiEntry.canManageSubscription()).thenReturn(true);
+    public void notNull_whenGetHelpUriString_shouldSetImageButtonVisible() {
+        when(mMockWifiEntry.getHelpUriString()).thenReturn(FAKE_URI_STRING);
         final WifiEntryPreference pref =
                 new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector);
         final LayoutInflater inflater = LayoutInflater.from(mContext);
         final View view = inflater.inflate(pref.getLayoutResource(), new LinearLayout(mContext),
                 false);
         final PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view);
+
         pref.onBindViewHolder(holder);
 
         assertThat(view.findViewById(R.id.icon_button).getVisibility()).isEqualTo(View.VISIBLE);
     }
 
     @Test
-    public void helpButton_whenCanManageSubscription_shouldSetCorrectContentDescription() {
-        when(mMockWifiEntry.canManageSubscription()).thenReturn(true);
+    public void helpButton_whenGetHelpUriStringNotNull_shouldSetCorrectContentDescription() {
+        when(mMockWifiEntry.getHelpUriString()).thenReturn(FAKE_URI_STRING);
         final WifiEntryPreference pref =
                 new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector);
         final LayoutInflater inflater = LayoutInflater.from(mContext);
diff --git a/packages/SystemUI/res/drawable/ic_device_air_purifier_off.xml b/packages/SystemUI/res/drawable/ic_device_air_purifier_off.xml
index b18c3e7..021c3ee 100644
--- a/packages/SystemUI/res/drawable/ic_device_air_purifier_off.xml
+++ b/packages/SystemUI/res/drawable/ic_device_air_purifier_off.xml
@@ -20,15 +20,15 @@
     android:viewportWidth="24"
     android:viewportHeight="24">
   <path
-      android:pathData="M16,19H5V7C5.0016,6.47 5.2128,5.9623 5.5875,5.5875C5.9623,5.2128 6.47,5.0016 7,5H14C14.5299,5.0016 15.0377,5.2128 15.4125,5.5875C15.7872,5.9623 15.9984,6.47 16,7V8H18V7C18,5.9391 17.5786,4.9217 16.8284,4.1716C16.0783,3.4214 15.0609,3 14,3H7C5.9391,3 4.9217,3.4214 4.1716,4.1716C3.4214,4.9217 3,5.9391 3,7V21H18V17H16V19Z"
+      android:pathData="M10,3H6C5.2043,3 4.4413,3.3161 3.8787,3.8787C3.3161,4.4413 3,5.2043 3,6V21H13V6C13,5.2043 12.6839,4.4413 12.1213,3.8787C11.5587,3.3161 10.7956,3 10,3ZM11,15H10V10H11V15ZM10,8C9.4696,8 8.9609,8.2107 8.5858,8.5858C8.2107,8.9609 8,9.4696 8,10V15C8,15.5304 8.2107,16.0391 8.5858,16.4142C8.9609,16.7893 9.4696,17 10,17H11V19H5V6C5,5.7348 5.1054,5.4804 5.2929,5.2929C5.4804,5.1054 5.7348,5 6,5H10C10.2652,5 10.5196,5.1054 10.7071,5.2929C10.8946,5.4804 11,5.7348 11,6V8H10Z"
       android:fillColor="#FF000000" />
   <path
-      android:pathData="M19,13C18.6049,13.378 18.1309,13.6638 17.6122,13.8367C17.0935,14.0096 16.5429,14.0653 16,14V16C16.5429,16.0653 17.0935,16.0096 17.6122,15.8367C18.1309,15.6638 18.6049,15.378 19,15C19.93,14.02 20,14 21,14V12C20,12 19.93,12.02 19,13Z"
+      android:pathData="M18.445,11.168C17.398,11.868 16.574,11.468 15.316,11.051L14.684,12.951C15.476,13.3014 16.3252,13.5047 17.19,13.551C18.0324,13.5503 18.8555,13.2994 19.555,12.83C20.596,12.135 21.383,12.514 22.684,12.947L23.316,11.047C22.688,10.842 20.57,9.753 18.445,11.168Z"
       android:fillColor="#FF000000" />
   <path
-      android:pathData="M19,9C18.6049,9.378 18.1309,9.6638 17.6122,9.8367C17.0935,10.0096 16.5429,10.0653 16,10V12C16.5429,12.0653 17.0935,12.0096 17.6122,11.8367C18.1309,11.6638 18.6049,11.378 19,11C19.93,10.02 20,10 21,10V8C20,8 19.93,8.02 19,9Z"
+      android:pathData="M17.189,9.659C18.0316,9.658 18.855,9.4071 19.555,8.938C20.596,8.244 21.384,8.622 22.684,9.055L23.316,7.155C22.727,6.955 20.583,5.8489 18.445,7.2719C17.406,7.966 16.615,7.59 15.317,7.156L14.683,9.056C15.4748,9.4074 16.324,9.6117 17.189,9.659V9.659Z"
       android:fillColor="#FF000000" />
   <path
-      android:pathData="M10.5,8C9.7089,8 8.9355,8.2346 8.2777,8.6741C7.6199,9.1137 7.1072,9.7383 6.8045,10.4692C6.5017,11.2001 6.4225,12.0044 6.5769,12.7803C6.7312,13.5563 7.1122,14.269 7.6716,14.8284C8.231,15.3878 8.9437,15.7688 9.7196,15.9232C10.4956,16.0775 11.2998,15.9982 12.0307,15.6955C12.7616,15.3927 13.3864,14.8801 13.8259,14.2223C14.2654,13.5645 14.5,12.7911 14.5,12C14.5,10.9391 14.0786,9.9217 13.3284,9.1716C12.5783,8.4214 11.5609,8 10.5,8ZM10.5,14C10.1044,14 9.7178,13.8827 9.3889,13.663C9.06,13.4432 8.8036,13.1308 8.6522,12.7654C8.5009,12.3999 8.4613,11.9978 8.5384,11.6098C8.6156,11.2218 8.8061,10.8655 9.0858,10.5858C9.3655,10.3061 9.7219,10.1156 10.1098,10.0385C10.4978,9.9613 10.8999,10.0008 11.2654,10.1522C11.6308,10.3036 11.9432,10.56 12.1629,10.8889C12.3827,11.2178 12.5,11.6044 12.5,12C12.5,12.5304 12.2893,13.0391 11.9142,13.4142C11.5391,13.7893 11.0304,14 10.5,14Z"
+      android:pathData="M18.445,15.063C17.41,15.752 16.634,15.384 15.316,14.945L14.684,16.845C15.4762,17.1948 16.3253,17.3981 17.19,17.445C18.0322,17.4438 18.8551,17.1932 19.555,16.725C20.594,16.031 21.385,16.407 22.683,16.841L23.317,14.941C22.728,14.749 20.583,13.637 18.445,15.063Z"
       android:fillColor="#FF000000" />
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_air_purifier_on.xml b/packages/SystemUI/res/drawable/ic_device_air_purifier_on.xml
index b18c3e7..9533cfe3 100644
--- a/packages/SystemUI/res/drawable/ic_device_air_purifier_on.xml
+++ b/packages/SystemUI/res/drawable/ic_device_air_purifier_on.xml
@@ -20,15 +20,18 @@
     android:viewportWidth="24"
     android:viewportHeight="24">
   <path
-      android:pathData="M16,19H5V7C5.0016,6.47 5.2128,5.9623 5.5875,5.5875C5.9623,5.2128 6.47,5.0016 7,5H14C14.5299,5.0016 15.0377,5.2128 15.4125,5.5875C15.7872,5.9623 15.9984,6.47 16,7V8H18V7C18,5.9391 17.5786,4.9217 16.8284,4.1716C16.0783,3.4214 15.0609,3 14,3H7C5.9391,3 4.9217,3.4214 4.1716,4.1716C3.4214,4.9217 3,5.9391 3,7V21H18V17H16V19Z"
+      android:pathData="M10,3H6C5.2043,3 4.4413,3.3161 3.8787,3.8787C3.3161,4.4413 3,5.2043 3,6V21H13V17H10C9.4696,17 8.9609,16.7893 8.5858,16.4142C8.2107,16.0391 8,15.5304 8,15V10C8,9.4696 8.2107,8.9609 8.5858,8.5858C8.9609,8.2107 9.4696,8 10,8H13V6C13,5.2043 12.6839,4.4413 12.1213,3.8787C11.5587,3.3161 10.7956,3 10,3Z"
       android:fillColor="#FF000000" />
   <path
-      android:pathData="M19,13C18.6049,13.378 18.1309,13.6638 17.6122,13.8367C17.0935,14.0096 16.5429,14.0653 16,14V16C16.5429,16.0653 17.0935,16.0096 17.6122,15.8367C18.1309,15.6638 18.6049,15.378 19,15C19.93,14.02 20,14 21,14V12C20,12 19.93,12.02 19,13Z"
+      android:pathData="M13,10H10V15H13V10Z"
       android:fillColor="#FF000000" />
   <path
-      android:pathData="M19,9C18.6049,9.378 18.1309,9.6638 17.6122,9.8367C17.0935,10.0096 16.5429,10.0653 16,10V12C16.5429,12.0653 17.0935,12.0096 17.6122,11.8367C18.1309,11.6638 18.6049,11.378 19,11C19.93,10.02 20,10 21,10V8C20,8 19.93,8.02 19,9Z"
+      android:pathData="M18.445,11.168C17.398,11.868 16.574,11.468 15.316,11.051L14.684,12.951C15.476,13.3014 16.3252,13.5047 17.19,13.551C18.0324,13.5503 18.8555,13.2994 19.555,12.83C20.596,12.135 21.383,12.514 22.684,12.947L23.316,11.047C22.688,10.842 20.57,9.753 18.445,11.168Z"
       android:fillColor="#FF000000" />
   <path
-      android:pathData="M10.5,8C9.7089,8 8.9355,8.2346 8.2777,8.6741C7.6199,9.1137 7.1072,9.7383 6.8045,10.4692C6.5017,11.2001 6.4225,12.0044 6.5769,12.7803C6.7312,13.5563 7.1122,14.269 7.6716,14.8284C8.231,15.3878 8.9437,15.7688 9.7196,15.9232C10.4956,16.0775 11.2998,15.9982 12.0307,15.6955C12.7616,15.3927 13.3864,14.8801 13.8259,14.2223C14.2654,13.5645 14.5,12.7911 14.5,12C14.5,10.9391 14.0786,9.9217 13.3284,9.1716C12.5783,8.4214 11.5609,8 10.5,8ZM10.5,14C10.1044,14 9.7178,13.8827 9.3889,13.663C9.06,13.4432 8.8036,13.1308 8.6522,12.7654C8.5009,12.3999 8.4613,11.9978 8.5384,11.6098C8.6156,11.2218 8.8061,10.8655 9.0858,10.5858C9.3655,10.3061 9.7219,10.1156 10.1098,10.0385C10.4978,9.9613 10.8999,10.0008 11.2654,10.1522C11.6308,10.3036 11.9432,10.56 12.1629,10.8889C12.3827,11.2178 12.5,11.6044 12.5,12C12.5,12.5304 12.2893,13.0391 11.9142,13.4142C11.5391,13.7893 11.0304,14 10.5,14Z"
+      android:pathData="M17.189,9.659C18.0316,9.658 18.855,9.4071 19.555,8.938C20.596,8.244 21.384,8.622 22.684,9.055L23.316,7.155C22.727,6.955 20.583,5.8489 18.445,7.2719C17.406,7.966 16.615,7.59 15.317,7.156L14.683,9.056C15.4748,9.4074 16.324,9.6117 17.189,9.659V9.659Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M18.445,15.063C17.41,15.752 16.634,15.384 15.316,14.945L14.684,16.845C15.4762,17.1948 16.3253,17.3981 17.19,17.445C18.0322,17.4438 18.8551,17.1932 19.555,16.725C20.594,16.031 21.385,16.407 22.683,16.841L23.317,14.941C22.728,14.749 20.583,13.637 18.445,15.063Z"
       android:fillColor="#FF000000" />
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_cooking_off.xml b/packages/SystemUI/res/drawable/ic_device_cooking_off.xml
new file mode 100644
index 0000000..272a3bb
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_device_cooking_off.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M9,16H2V18H9V21H11V18C11,17.4696 10.7893,16.9609 10.4142,16.5858C10.0391,16.2107 9.5304,16 9,16Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M15,16C14.4696,16 13.9609,16.2107 13.5858,16.5858C13.2107,16.9609 13,17.4696 13,18V21H15V18H22V16H15Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M6,15H18C18.7956,15 19.5587,14.6839 20.1213,14.1213C20.6839,13.5587 21,12.7956 21,12V8H3V12C3,12.7956 3.3161,13.5587 3.8787,14.1213C4.4413,14.6839 5.2043,15 6,15ZM5,10H19V12C19,12.2652 18.8946,12.5196 18.7071,12.7072C18.5196,12.8947 18.2652,13 18,13H6C5.7348,13 5.4804,12.8947 5.2929,12.7072C5.1054,12.5196 5,12.2652 5,12V10Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M21,5H15V4C15,3.7348 14.8946,3.4804 14.7071,3.2929C14.5196,3.1053 14.2652,3 14,3H10C9.7348,3 9.4804,3.1053 9.2929,3.2929C9.1054,3.4804 9,3.7348 9,4V5H3V7H21V5Z"
+      android:fillColor="#FF000000" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_cooking_on.xml b/packages/SystemUI/res/drawable/ic_device_cooking_on.xml
new file mode 100644
index 0000000..3785f8b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_device_cooking_on.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M9,16H2V18H9V21H11V18C11,17.4696 10.7893,16.9609 10.4142,16.5858C10.0391,16.2107 9.5304,16 9,16Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M13,18V21H15V18H22V16H15C14.4696,16 13.9609,16.2107 13.5858,16.5858C13.2107,16.9609 13,17.4696 13,18Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M3,12C3,12.7956 3.3161,13.5587 3.8787,14.1213C4.4413,14.6839 5.2043,15 6,15H18C18.7956,15 19.5587,14.6839 20.1213,14.1213C20.6839,13.5587 21,12.7956 21,12V8H3V12Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M21,5H15V4C15,3.7348 14.8946,3.4804 14.7071,3.2929C14.5196,3.1053 14.2652,3 14,3H10C9.7348,3 9.4804,3.1053 9.2929,3.2929C9.1054,3.4804 9,3.7348 9,4V5H3V7H21V5Z"
+      android:fillColor="#FF000000" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_display_off.xml b/packages/SystemUI/res/drawable/ic_device_display_off.xml
new file mode 100644
index 0000000..07737c9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_device_display_off.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M20,3H4C3.4701,3.0016 2.9623,3.2129 2.5875,3.5877C2.2128,3.9624 2.0016,4.47 2,5V17C2.0016,17.5299 2.2128,18.0376 2.5875,18.4124C2.9623,18.7871 3.4701,18.9984 4,19H8V21H16V19H20C20.5287,18.9974 21.0348,18.7854 21.4078,18.4106C21.7807,18.0359 21.99,17.5287 21.99,17L22,5C21.9984,4.47 21.7872,3.9624 21.4125,3.5877C21.0377,3.2129 20.5299,3.0016 20,3V3ZM20,17H4V5H20V17Z"
+      android:fillColor="#FF000000" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_display_on.xml b/packages/SystemUI/res/drawable/ic_device_display_on.xml
new file mode 100644
index 0000000..2416f6b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_device_display_on.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M20,3H4C3.4701,3.0016 2.9623,3.2129 2.5875,3.5877C2.2128,3.9624 2.0016,4.47 2,5V17C2.0016,17.5299 2.2128,18.0376 2.5875,18.4124C2.9623,18.7871 3.4701,18.9984 4,19H8V21H16V19H20C20.5287,18.9974 21.0348,18.7854 21.4078,18.4106C21.7807,18.0359 21.99,17.5287 21.99,17L22,5C21.9984,4.47 21.7872,3.9624 21.4125,3.5877C21.0377,3.2129 20.5299,3.0016 20,3V3ZM20,17H4V5H20V17Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M19,6H5V16H19V6Z"
+      android:fillColor="#FF000000" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_door_off.xml b/packages/SystemUI/res/drawable/ic_device_door_off.xml
new file mode 100644
index 0000000..291f312
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_device_door_off.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M7,14H5C4.7348,14 4.4804,14.1054 4.2929,14.293C4.1054,14.4805 4,14.7348 4,15C4,15.2652 4.1054,15.5195 4.2929,15.707C4.4804,15.8946 4.7348,16 5,16H7V21H17V3H7V14ZM15,5V19H9V16H12C12.2652,16 12.5196,15.8946 12.7071,15.707C12.8946,15.5195 13,15.2652 13,15C13,14.7348 12.8946,14.4805 12.7071,14.293C12.5196,14.1054 12.2652,14 12,14H9V5H15Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M20,3C19.7348,3 19.4804,3.1054 19.2929,3.293C19.1054,3.4805 19,3.7348 19,4V8C19,8.2652 19.1054,8.5195 19.2929,8.707C19.4804,8.8946 19.7348,9 20,9C20.2652,9 20.5196,8.8946 20.7071,8.707C20.8946,8.5195 21,8.2652 21,8V4C21,3.7348 20.8946,3.4805 20.7071,3.293C20.5196,3.1054 20.2652,3 20,3Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M12,10C12.5523,10 13,9.5523 13,9C13,8.4477 12.5523,8 12,8C11.4477,8 11,8.4477 11,9C11,9.5523 11.4477,10 12,10Z"
+      android:fillColor="#FF000000" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_door_on.xml b/packages/SystemUI/res/drawable/ic_device_door_on.xml
new file mode 100644
index 0000000..e6cdf11
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_device_door_on.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M20,3C19.7348,3 19.4804,3.1054 19.2929,3.293C19.1054,3.4805 19,3.7348 19,4V8C19,8.2652 19.1054,8.5195 19.2929,8.707C19.4804,8.8946 19.7348,9 20,9C20.2652,9 20.5196,8.8946 20.7071,8.707C20.8946,8.5195 21,8.2652 21,8V4C21,3.7348 20.8946,3.4805 20.7071,3.293C20.5196,3.1054 20.2652,3 20,3Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M13,15C13,14.7348 12.8946,14.4805 12.7071,14.293C12.5196,14.1054 12.2652,14 12,14H5C4.7348,14 4.4804,14.1054 4.2929,14.293C4.1054,14.4805 4,14.7348 4,15C4,15.2652 4.1054,15.5195 4.2929,15.707C4.4804,15.8946 4.7348,16 5,16H12C12.2652,16 12.5196,15.8946 12.7071,15.707C12.8946,15.5195 13,15.2652 13,15Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M7,13H12C12.5304,13 13.0391,13.2109 13.4142,13.5859C13.7893,13.961 14,14.4696 14,15C14,15.5304 13.7893,16.039 13.4142,16.4141C13.0391,16.7891 12.5304,17 12,17H7V21H17V3H7V13ZM12,8C12.1978,8 12.3911,8.0586 12.5556,8.1685C12.72,8.2783 12.8482,8.4345 12.9239,8.6172C12.9996,8.7999 13.0194,9.0013 12.9808,9.1953C12.9422,9.3893 12.847,9.5672 12.7071,9.707C12.5673,9.8469 12.3891,9.9424 12.1951,9.981C12.0011,10.0195 11.8,9.9995 11.6173,9.9238C11.4346,9.8481 11.2784,9.7201 11.1685,9.5557C11.0586,9.3912 11,9.1978 11,9C11,8.7348 11.1054,8.4805 11.2929,8.293C11.4804,8.1054 11.7348,8 12,8Z"
+      android:fillColor="#FF000000" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_garage_on.xml b/packages/SystemUI/res/drawable/ic_device_garage_on.xml
index 8865983..eeb4bc1 100644
--- a/packages/SystemUI/res/drawable/ic_device_garage_on.xml
+++ b/packages/SystemUI/res/drawable/ic_device_garage_on.xml
@@ -20,9 +20,12 @@
     android:viewportWidth="24"
     android:viewportHeight="24">
   <path
-      android:pathData="M20,9L12,3L4,9V21H6V10L12,5.5L18,10V21H20V9Z"
+      android:pathData="M12,3L4,9V21H7V11H17V21H20V9L12,3Z"
       android:fillColor="#FF000000" />
   <path
-      android:pathData="M7,11V21H17V11H7ZM15,13V15H9V13H15ZM9,19V17H15V19H9Z"
+      android:pathData="M15,13H9V15H15V13Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M15,17H9V19H15V17Z"
       android:fillColor="#FF000000" />
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_outdoor_garden_off.xml b/packages/SystemUI/res/drawable/ic_device_outdoor_garden_off.xml
new file mode 100644
index 0000000..0d98f9e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_device_outdoor_garden_off.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M18,3L15,5.25L12,3L9,5.25L6,3L2,6V21H22V6L18,3ZM8,19H4V7L6,5.5L8,7V19ZM14,19H10V7L12,5.5L14,7V19ZM20,19H16V7L18,5.5L20,7V19Z"
+      android:fillColor="#FF000000" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_outdoor_garden_on.xml b/packages/SystemUI/res/drawable/ic_device_outdoor_garden_on.xml
new file mode 100644
index 0000000..00b6af6
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_device_outdoor_garden_on.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M2,6V21H8V6L5,3L2,6Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M9,6V21H15V6L12,3L9,6Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M19,3L16,6V21H22V6L19,3Z"
+      android:fillColor="#FF000000" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_pergola_on.xml b/packages/SystemUI/res/drawable/ic_device_pergola_on.xml
index b7113dc..cbbee8c 100644
--- a/packages/SystemUI/res/drawable/ic_device_pergola_on.xml
+++ b/packages/SystemUI/res/drawable/ic_device_pergola_on.xml
@@ -20,7 +20,7 @@
     android:viewportWidth="24"
     android:viewportHeight="24">
   <path
-      android:pathData="M20,2C19.7348,2 19.4804,2.1054 19.2929,2.293C19.1054,2.4805 19,2.7348 19,3V4H5V3C5,2.7348 4.8946,2.4805 4.7071,2.293C4.5196,2.1054 4.2652,2 4,2C3.7348,2 3.4804,2.1054 3.2929,2.293C3.1054,2.4805 3,2.7348 3,3V21H5V10H19V21H21V3C21,2.7348 20.8946,2.4805 20.7071,2.293C20.5196,2.1054 20.2652,2 20,2ZM5,8V6H19V8H5Z"
+      android:pathData="M20,2C19.7348,2 19.4804,2.1054 19.2929,2.293C19.1054,2.4805 19,2.7348 19,3V4H5V3C5,2.7348 4.8946,2.4805 4.7071,2.293C4.5196,2.1054 4.2652,2 4,2C3.7348,2 3.4804,2.1054 3.2929,2.293C3.1054,2.4805 3,2.7348 3,3V21H5V10H19V21H21V3C21,2.7348 20.8946,2.4805 20.7071,2.293C20.5196,2.1054 20.2652,2 20,2Z"
       android:fillColor="#FF000000" />
   <path
       android:pathData="M8,18H11V21H13V18H16V16H8V18Z"
diff --git a/packages/SystemUI/res/drawable/ic_device_thermostat_off.xml b/packages/SystemUI/res/drawable/ic_device_thermostat_off.xml
index 1ba8741..27da211 100644
--- a/packages/SystemUI/res/drawable/ic_device_thermostat_off.xml
+++ b/packages/SystemUI/res/drawable/ic_device_thermostat_off.xml
@@ -20,6 +20,6 @@
     android:viewportWidth="24"
     android:viewportHeight="24">
   <path
-      android:pathData="M16,18.97C16.6469,18.1148 16.9979,17.0723 17,16C16.9993,15.2239 16.8183,14.4586 16.4712,13.7644C16.1241,13.0702 15.6205,12.4662 15,12V6C15,5.2043 14.6839,4.4413 14.1213,3.8787C13.5587,3.3161 12.7956,3 12,3C11.2044,3 10.4413,3.3161 9.8787,3.8787C9.3161,4.4413 9,5.2043 9,6V12C8.3795,12.4662 7.8759,13.0702 7.5288,13.7644C7.1817,14.4586 7.0007,15.2239 7,16C7.0021,17.0723 7.3531,18.1148 8,18.97V19H8.02C8.4815,19.6206 9.0818,20.1246 9.7729,20.4719C10.4639,20.8192 11.2266,21.0001 12,21.0001C12.7734,21.0001 13.5361,20.8192 14.2271,20.4719C14.9182,20.1246 15.5185,19.6206 15.98,19H16V18.97ZM10.2,13.6L11,13V6C11,5.7348 11.1054,5.4804 11.2929,5.2929C11.4804,5.1054 11.7348,5 12,5C12.2652,5 12.5196,5.1054 12.7071,5.2929C12.8946,5.4804 13,5.7348 13,6V13L13.8,13.6C14.1711,13.8809 14.4723,14.2435 14.6805,14.6598C14.8886,15.076 14.9979,15.5346 15,16H9C9.0009,15.5344 9.1098,15.0754 9.318,14.659C9.5262,14.2426 9.8281,13.8801 10.2,13.6Z"
+      android:pathData="M15,12V6C15,5.2043 14.6839,4.4413 14.1213,3.8787C13.5587,3.3161 12.7956,3 12,3C11.2044,3 10.4413,3.3161 9.8787,3.8787C9.3161,4.4413 9,5.2043 9,6V12C8.3795,12.4662 7.8759,13.0702 7.5288,13.7644C7.1817,14.4586 7.0007,15.2239 7,16C7.0021,17.0723 7.3531,18.1148 8,18.97V19H8.02C8.4815,19.6206 9.0818,20.1246 9.7729,20.4719C10.4639,20.8192 11.2266,21.0001 12,21.0001C12.7734,21.0001 13.5361,20.8192 14.2271,20.4719C14.9182,20.1246 15.5185,19.6206 15.98,19H16V18.97C16.6469,18.1148 16.9979,17.0723 17,16C16.9993,15.2239 16.8183,14.4586 16.4712,13.7644C16.1241,13.0702 15.6205,12.4662 15,12ZM14.4,17.773L14.367,17.819C14.0952,18.1807 13.744,18.4752 13.3405,18.6799C12.937,18.8846 12.4919,18.9941 12.0395,18.9998C11.5871,19.0055 11.1394,18.9073 10.7309,18.7128C10.3223,18.5184 9.9638,18.2327 9.683,17.878L9.604,17.778C9.2154,17.2664 9.0034,16.6425 9,16C9.0016,15.5346 9.1108,15.0758 9.3189,14.6595C9.5271,14.2432 9.8286,13.8805 10.2,13.6L11,13V6C11,5.7348 11.1054,5.4804 11.2929,5.2929C11.4804,5.1054 11.7348,5 12,5C12.2652,5 12.5196,5.1054 12.7071,5.2929C12.8946,5.4804 13,5.7348 13,6V13L13.8,13.6C14.1714,13.8805 14.4729,14.2432 14.6811,14.6595C14.8892,15.0758 14.9984,15.5346 15,16C14.9967,16.6403 14.7862,17.2623 14.4,17.773V17.773Z"
       android:fillColor="#FF000000" />
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_thermostat_on.xml b/packages/SystemUI/res/drawable/ic_device_thermostat_on.xml
index 1ba8741..deabb0e 100644
--- a/packages/SystemUI/res/drawable/ic_device_thermostat_on.xml
+++ b/packages/SystemUI/res/drawable/ic_device_thermostat_on.xml
@@ -20,6 +20,6 @@
     android:viewportWidth="24"
     android:viewportHeight="24">
   <path
-      android:pathData="M16,18.97C16.6469,18.1148 16.9979,17.0723 17,16C16.9993,15.2239 16.8183,14.4586 16.4712,13.7644C16.1241,13.0702 15.6205,12.4662 15,12V6C15,5.2043 14.6839,4.4413 14.1213,3.8787C13.5587,3.3161 12.7956,3 12,3C11.2044,3 10.4413,3.3161 9.8787,3.8787C9.3161,4.4413 9,5.2043 9,6V12C8.3795,12.4662 7.8759,13.0702 7.5288,13.7644C7.1817,14.4586 7.0007,15.2239 7,16C7.0021,17.0723 7.3531,18.1148 8,18.97V19H8.02C8.4815,19.6206 9.0818,20.1246 9.7729,20.4719C10.4639,20.8192 11.2266,21.0001 12,21.0001C12.7734,21.0001 13.5361,20.8192 14.2271,20.4719C14.9182,20.1246 15.5185,19.6206 15.98,19H16V18.97ZM10.2,13.6L11,13V6C11,5.7348 11.1054,5.4804 11.2929,5.2929C11.4804,5.1054 11.7348,5 12,5C12.2652,5 12.5196,5.1054 12.7071,5.2929C12.8946,5.4804 13,5.7348 13,6V13L13.8,13.6C14.1711,13.8809 14.4723,14.2435 14.6805,14.6598C14.8886,15.076 14.9979,15.5346 15,16H9C9.0009,15.5344 9.1098,15.0754 9.318,14.659C9.5262,14.2426 9.8281,13.8801 10.2,13.6Z"
+      android:pathData="M15,12V6C15,5.2043 14.6839,4.4413 14.1213,3.8787C13.5587,3.3161 12.7956,3 12,3C11.2044,3 10.4413,3.3161 9.8787,3.8787C9.3161,4.4413 9,5.2043 9,6V12C8.3795,12.4662 7.8759,13.0702 7.5288,13.7644C7.1817,14.4586 7.0007,15.2239 7,16C7.0021,17.0723 7.3531,18.1148 8,18.97V19H8.02C8.4815,19.6206 9.0818,20.1246 9.7729,20.4719C10.4639,20.8192 11.2266,21.0001 12,21.0001C12.7734,21.0001 13.5361,20.8192 14.2271,20.4719C14.9182,20.1246 15.5185,19.6206 15.98,19H16V18.97C16.6469,18.1148 16.9979,17.0723 17,16C16.9993,15.2239 16.8183,14.4586 16.4712,13.7644C16.1241,13.0702 15.6205,12.4662 15,12V12ZM11,6C11,5.7348 11.1054,5.4804 11.2929,5.2929C11.4804,5.1054 11.7348,5 12,5C12.2652,5 12.5196,5.1054 12.7071,5.2929C12.8946,5.4804 13,5.7348 13,6V10H11V6Z"
       android:fillColor="#FF000000" />
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_tv_off.xml b/packages/SystemUI/res/drawable/ic_device_tv_off.xml
index dd91ed8..f0c9b56 100644
--- a/packages/SystemUI/res/drawable/ic_device_tv_off.xml
+++ b/packages/SystemUI/res/drawable/ic_device_tv_off.xml
@@ -20,6 +20,6 @@
     android:viewportWidth="24"
     android:viewportHeight="24">
   <path
-      android:pathData="M20,4H4C3.4701,4.0016 2.9623,4.2129 2.5875,4.5876C2.2128,4.9624 2.0016,5.47 2,6V17C2.0016,17.5299 2.2128,18.0376 2.5875,18.4124C2.9623,18.7871 3.4701,18.9984 4,19V21H5L5.667,19H18.333L19,21H20V19C20.5299,18.9984 21.0377,18.7871 21.4125,18.4124C21.7872,18.0376 21.9984,17.5299 22,17V6C21.9984,5.47 21.7872,4.9624 21.4125,4.5876C21.0377,4.2129 20.5299,4.0016 20,4ZM20,17H4V6H20V17Z"
+      android:pathData="M20,4H4C3.4701,4.0016 2.9623,4.2129 2.5875,4.5876C2.2128,4.9624 2.0016,5.47 2,6V17C2.0016,17.5299 2.2128,18.0376 2.5875,18.4124C2.9623,18.7871 3.4701,18.9984 4,19V21H5L5.667,19H18.333L19,21H20V19C20.5299,18.9984 21.0377,18.7871 21.4125,18.4124C21.7872,18.0376 21.9984,17.5299 22,17V6C21.9984,5.47 21.7872,4.9624 21.4125,4.5876C21.0377,4.2129 20.5299,4.0016 20,4V4ZM20,17H4V6H20V17Z"
       android:fillColor="#FF000000" />
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_tv_on.xml b/packages/SystemUI/res/drawable/ic_device_tv_on.xml
index dd91ed8..ed625e94 100644
--- a/packages/SystemUI/res/drawable/ic_device_tv_on.xml
+++ b/packages/SystemUI/res/drawable/ic_device_tv_on.xml
@@ -20,6 +20,9 @@
     android:viewportWidth="24"
     android:viewportHeight="24">
   <path
-      android:pathData="M20,4H4C3.4701,4.0016 2.9623,4.2129 2.5875,4.5876C2.2128,4.9624 2.0016,5.47 2,6V17C2.0016,17.5299 2.2128,18.0376 2.5875,18.4124C2.9623,18.7871 3.4701,18.9984 4,19V21H5L5.667,19H18.333L19,21H20V19C20.5299,18.9984 21.0377,18.7871 21.4125,18.4124C21.7872,18.0376 21.9984,17.5299 22,17V6C21.9984,5.47 21.7872,4.9624 21.4125,4.5876C21.0377,4.2129 20.5299,4.0016 20,4ZM20,17H4V6H20V17Z"
+      android:pathData="M20,4H4C3.4701,4.0016 2.9623,4.2129 2.5875,4.5876C2.2128,4.9624 2.0016,5.47 2,6V17C2.0016,17.5299 2.2128,18.0376 2.5875,18.4124C2.9623,18.7871 3.4701,18.9984 4,19V21H5L5.667,19H18.333L19,21H20V19C20.5299,18.9984 21.0377,18.7871 21.4125,18.4124C21.7872,18.0376 21.9984,17.5299 22,17V6C21.9984,5.47 21.7872,4.9624 21.4125,4.5876C21.0377,4.2129 20.5299,4.0016 20,4V4ZM20,17H4V6H20V17Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M19,7H5V16H19V7Z"
       android:fillColor="#FF000000" />
 </vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml
deleted file mode 100644
index 24e0635..0000000
--- a/packages/SystemUI/res/drawable/ic_device_unknown_gm2_24px.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
-  <path
-      android:fillColor="#FF000000"
-      android:pathData="M12,2l-5.5,9h11L12,2zM12,5.84L13.93,9h-3.87L12,5.84zM17.5,13c-2.49,0 -4.5,2.01 -4.5,4.5s2.01,4.5 4.5,4.5 4.5,-2.01 4.5,-4.5 -2.01,-4.5 -4.5,-4.5zM17.5,20c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5zM3,21.5h8v-8L3,13.5v8zM5,15.5h4v4L5,19.5v-4z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_unknown_off.xml b/packages/SystemUI/res/drawable/ic_device_unknown_off.xml
new file mode 100644
index 0000000..55820d0
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_device_unknown_off.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M8,6.5V11.76C8.8027,12.2963 9.4117,13.0766 9.7369,13.9856C10.0622,14.8946 10.0865,15.8841 9.8062,16.8079C9.526,17.7317 8.9561,18.541 8.1807,19.1161C7.4052,19.6912 6.4654,20.0017 5.5,20.0017C4.5346,20.0017 3.5948,19.6912 2.8193,19.1161C2.0439,18.541 1.474,17.7317 1.1938,16.8079C0.9135,15.8841 0.9378,14.8946 1.2631,13.9856C1.5883,13.0766 2.1973,12.2963 3,11.76V6.5C3,5.837 3.2634,5.2011 3.7322,4.7322C4.2011,4.2634 4.837,4 5.5,4C6.163,4 6.7989,4.2634 7.2678,4.7322C7.7366,5.2011 8,5.837 8,6.5ZM3.049,16H7.949C8.0467,15.5132 7.9978,15.0084 7.8084,14.5495C7.619,14.0905 7.2976,13.6981 6.885,13.422L5.998,12.828V6.5C5.998,6.3674 5.9453,6.2402 5.8516,6.1465C5.7578,6.0527 5.6306,6 5.498,6C5.3654,6 5.2382,6.0527 5.1445,6.1465C5.0507,6.2402 4.998,6.3674 4.998,6.5V12.828L4.111,13.422C3.7061,13.6922 3.3887,14.0746 3.1976,14.5223C3.0065,14.97 2.95,15.4637 3.035,15.943C3.0363,15.951 3.0389,15.959 3.0416,15.967C3.0453,15.978 3.049,15.989 3.049,16Z"
+      android:fillColor="#FF000000"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M14,16H20V18H14V16Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M17,4C15.7113,4.0006 14.457,4.416 13.4228,5.1849C12.3886,5.9538 11.6294,7.0353 11.2577,8.2692C10.8859,9.5031 10.9214,10.824 11.3587,12.0362C11.7961,13.2484 12.6121,14.2876 13.686,15H20.314C21.3879,14.2876 22.204,13.2484 22.6413,12.0362C23.0786,10.824 23.1141,9.5031 22.7423,8.2692C22.3706,7.0353 21.6114,5.9538 20.5772,5.1849C19.543,4.416 18.2887,4.0006 17,4ZM19.643,13H14.357C13.7469,12.4629 13.3149,11.7528 13.1185,10.9641C12.9221,10.1753 12.9707,9.3455 13.2577,8.5851C13.5447,7.8246 14.0566,7.1697 14.7251,6.7074C15.3937,6.2452 16.1872,5.9976 17,5.9976C17.8128,5.9976 18.6063,6.2452 19.2749,6.7074C19.9434,7.1697 20.4553,7.8246 20.7423,8.5851C21.0293,9.3455 21.0779,10.1753 20.8815,10.9641C20.6851,11.7528 20.2531,12.4629 19.643,13Z"
+      android:fillColor="#FF000000"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M18.0607,19.5607C17.7793,19.842 17.3978,20 17,20C16.6022,20 16.2207,19.842 15.9393,19.5607C15.658,19.2794 15.5,18.8978 15.5,18.5H18.5C18.5,18.8978 18.342,19.2794 18.0607,19.5607Z"
+      android:fillColor="#FF000000" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_unknown_on.xml b/packages/SystemUI/res/drawable/ic_device_unknown_on.xml
new file mode 100644
index 0000000..08d9817
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_device_unknown_on.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M8,6.5V11.76C8.8027,12.2963 9.4117,13.0766 9.7369,13.9856C10.0622,14.8946 10.0865,15.8841 9.8062,16.8079C9.526,17.7317 8.9561,18.541 8.1807,19.1161C7.4052,19.6912 6.4654,20.0017 5.5,20.0017C4.5346,20.0017 3.5948,19.6912 2.8193,19.1161C2.0439,18.541 1.474,17.7317 1.1938,16.8079C0.9135,15.8841 0.9378,14.8946 1.2631,13.9856C1.5883,13.0766 2.1973,12.2963 3,11.76V6.5C3,5.837 3.2634,5.2011 3.7322,4.7322C4.2011,4.2634 4.837,4 5.5,4C6.163,4 6.7989,4.2634 7.2678,4.7322C7.7366,5.2011 8,5.837 8,6.5ZM3.049,16H7.949C8.0469,15.5134 7.9983,15.0087 7.8093,14.5498C7.6202,14.0909 7.2992,13.6984 6.887,13.422L6,12.828V6.5C6,6.3674 5.9473,6.2402 5.8535,6.1465C5.7598,6.0527 5.6326,6 5.5,6C5.3674,6 5.2402,6.0527 5.1465,6.1465C5.0527,6.2402 5,6.3674 5,6.5V12.828L4.111,13.422C3.7061,13.6922 3.3887,14.0746 3.1976,14.5223C3.0065,14.97 2.95,15.4637 3.035,15.943C3.0363,15.951 3.0389,15.959 3.0416,15.967C3.0453,15.978 3.049,15.989 3.049,16Z"
+      android:fillColor="#FF000000"
+      android:fillType="evenOdd"/>
+  <path
+      android:pathData="M20,16V18H14V16H20Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M17,4C15.7113,4.0006 14.457,4.416 13.4228,5.1849C12.3886,5.9538 11.6294,7.0353 11.2577,8.2692C10.8859,9.5031 10.9214,10.824 11.3587,12.0362C11.796,13.2484 12.6121,14.2876 13.686,15H20.314C21.3879,14.2876 22.204,13.2484 22.6413,12.0362C23.0786,10.824 23.1141,9.5031 22.7423,8.2692C22.3706,7.0353 21.6114,5.9538 20.5772,5.1849C19.543,4.416 18.2887,4.0006 17,4Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M18.0607,19.5607C17.7794,19.842 17.3978,20 17,20C16.6022,20 16.2206,19.842 15.9393,19.5607C15.658,19.2794 15.5,18.8978 15.5,18.5H18.5C18.5,18.8978 18.342,19.2794 18.0607,19.5607Z"
+      android:fillColor="#FF000000" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_water_off.xml b/packages/SystemUI/res/drawable/ic_device_water_off.xml
new file mode 100644
index 0000000..e1a7846
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_device_water_off.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M17.654,7.563L12,2L6.346,7.563C5.6036,8.2877 5.0136,9.1533 4.6108,10.1094C4.2079,11.0654 4.0002,12.0924 4,13.1299C4,15.2516 4.8429,17.2863 6.3432,18.7866C7.8434,20.2869 9.8783,21.1299 12,21.1299C14.1217,21.1299 16.1566,20.2869 17.6569,18.7866C19.1572,17.2863 20,15.2516 20,13.1299C20,12.0924 19.7925,11.0654 19.3896,10.1094C18.9867,9.1533 18.3966,8.2875 17.654,7.563ZM12,19C10.4265,19.0152 8.9113,18.4056 7.7865,17.3052C6.6617,16.2048 6.0192,14.7033 6,13.1299C5.9996,12.3577 6.1541,11.5933 6.4543,10.8818C6.7546,10.1704 7.1945,9.5262 7.748,8.9878L12,4.8061L16.252,8.9888C16.8056,9.5269 17.2456,10.171 17.5458,10.8823C17.8461,11.5936 18.0005,12.3578 18,13.1299C17.9807,14.7033 17.3383,16.2048 16.2135,17.3052C15.0887,18.4056 13.5735,19.0152 12,19Z"
+      android:fillColor="#FF000000" />
+  <path
+      android:pathData="M16,12C15.7348,12 15.4804,12.1054 15.2929,12.293C15.1054,12.4805 15,12.7348 15,13C15,13.7956 14.6839,14.5585 14.1213,15.1211C13.5587,15.6837 12.7956,16 12,16C11.7348,16 11.4804,16.1054 11.2929,16.293C11.1054,16.4805 11,16.7348 11,17C11,17.2652 11.1054,17.5195 11.2929,17.707C11.4804,17.8946 11.7348,18 12,18C13.3256,17.9984 14.5964,17.471 15.5338,16.5337C16.4711,15.5964 16.9984,14.3256 17,13C17,12.7348 16.8946,12.4805 16.7071,12.293C16.5196,12.1054 16.2652,12 16,12Z"
+      android:fillColor="#FF000000" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_device_water_on.xml b/packages/SystemUI/res/drawable/ic_device_water_on.xml
new file mode 100644
index 0000000..e57e053
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_device_water_on.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M17.654,7.563L12,2L6.346,7.563C5.6036,8.2877 5.0136,9.1533 4.6108,10.1094C4.2079,11.0654 4.0002,12.0924 4,13.1299C4.0174,15.2343 4.87,17.2458 6.3703,18.7217C7.8705,20.1975 9.8956,21.017 12,21C14.1044,21.017 16.1295,20.1975 17.6297,18.7217C19.13,17.2458 19.9826,15.2343 20,13.1299C20,12.0924 19.7925,11.0654 19.3896,10.1094C18.9867,9.1533 18.3966,8.2875 17.654,7.563ZM12,18C11.7348,18 11.4804,17.8946 11.2929,17.707C11.1054,17.5195 11,17.2652 11,17C11,16.7348 11.1054,16.4805 11.2929,16.293C11.4804,16.1054 11.7348,16 12,16C12.7956,16 13.5587,15.6837 14.1213,15.1211C14.6839,14.5585 15,13.7956 15,13C15,12.7348 15.1054,12.4805 15.2929,12.293C15.4804,12.1054 15.7348,12 16,12C16.2652,12 16.5196,12.1054 16.7071,12.293C16.8946,12.4805 17,12.7348 17,13C16.9984,14.3256 16.4711,15.5964 15.5338,16.5337C14.5964,17.471 13.3256,17.9984 12,18Z"
+      android:fillColor="#FF000000" />
+</vector>
diff --git a/packages/SystemUI/res/layout/app_ops_info.xml b/packages/SystemUI/res/layout/app_ops_info.xml
index bfa252c..8342a2a 100644
--- a/packages/SystemUI/res/layout/app_ops_info.xml
+++ b/packages/SystemUI/res/layout/app_ops_info.xml
@@ -19,8 +19,8 @@
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
+        android:focusable="true"
         android:id="@+id/app_ops_info"
-        android:clickable="true"
         android:clipChildren="false"
         android:clipToPadding="false"
         android:orientation="vertical"
diff --git a/packages/SystemUI/res/layout/bubble_dismiss_target.xml b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
index ca085b6..f5cd727 100644
--- a/packages/SystemUI/res/layout/bubble_dismiss_target.xml
+++ b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
@@ -17,7 +17,7 @@
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
-    android:layout_height="@dimen/pip_dismiss_gradient_height"
+    android:layout_height="@dimen/floating_dismiss_gradient_height"
     android:layout_gravity="bottom|center_horizontal">
 
     <FrameLayout
diff --git a/packages/SystemUI/res/layout/controls_spinner_item.xml b/packages/SystemUI/res/layout/controls_spinner_item.xml
index 00654c8..45540f1 100644
--- a/packages/SystemUI/res/layout/controls_spinner_item.xml
+++ b/packages/SystemUI/res/layout/controls_spinner_item.xml
@@ -30,6 +30,7 @@
         android:layout_gravity="center"
         android:layout_width="@dimen/controls_header_app_icon_size"
         android:layout_height="@dimen/controls_header_app_icon_size"
+        android:contentDescription="@null"
         android:layout_marginEnd="10dp" />
 
     <TextView
diff --git a/packages/SystemUI/res/layout/controls_with_favorites.xml b/packages/SystemUI/res/layout/controls_with_favorites.xml
index 91beeb8..b323209 100644
--- a/packages/SystemUI/res/layout/controls_with_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_with_favorites.xml
@@ -34,6 +34,7 @@
         android:orientation="horizontal"
         android:layout_width="0dp"
         android:layout_weight="1"
+        android:minHeight="48dp"
         android:layout_height="wrap_content"
         android:layout_gravity="center"
         android:gravity="center">
@@ -43,6 +44,7 @@
           android:layout_gravity="center"
           android:layout_width="@dimen/controls_header_app_icon_size"
           android:layout_height="@dimen/controls_header_app_icon_size"
+          android:contentDescription="@null"
           android:layout_marginEnd="10dp" />
 
       <TextView
@@ -72,6 +74,6 @@
       android:layout_height="wrap_content"
       android:orientation="vertical"
       android:paddingTop="30dp"
-      android:layout_marginLeft="@dimen/controls_list_side_margin"
-      android:layout_marginRight="@dimen/controls_list_side_margin" />
+      android:layout_marginLeft="@dimen/global_actions_side_margin"
+      android:layout_marginRight="@dimen/global_actions_side_margin" />
 </merge>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
index cb53fe6..72cc2dd 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
@@ -18,15 +18,16 @@
      work around this for now with LinearLayouts. -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
+    android:layout_width="0dp"
+    android:layout_weight="1"
     android:layout_height="wrap_content"
     android:gravity="center"
     android:paddingTop="@dimen/global_actions_grid_item_vertical_margin"
     android:paddingBottom="@dimen/global_actions_grid_item_vertical_margin"
     android:paddingLeft="@dimen/global_actions_grid_item_side_margin"
     android:paddingRight="@dimen/global_actions_grid_item_side_margin"
-    android:layout_marginRight="3dp"
-    android:layout_marginLeft="3dp"
+    android:layout_marginRight="@dimen/control_base_item_margin"
+    android:layout_marginLeft="@dimen/control_base_item_margin"
     android:background="@drawable/control_background">
     <LinearLayout
         android:layout_width="@dimen/global_actions_grid_item_width"
diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
index 620e2e6..59c4d01 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_v2.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
@@ -12,17 +12,17 @@
       android:layout_height="wrap_content"
       android:orientation="horizontal"
       android:theme="@style/qs_theme"
-      android:gravity="top | center_horizontal"
+      android:gravity="top"
       android:clipChildren="false"
       android:clipToPadding="false"
       android:layout_marginTop="@dimen/global_actions_top_margin"
   >
     <LinearLayout
         android:id="@android:id/list"
-        android:layout_width="wrap_content"
+        android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginLeft="@dimen/global_actions_grid_side_margin"
-        android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+        android:layout_marginLeft="@dimen/global_actions_side_margin"
+        android:layout_marginRight="@dimen/global_actions_side_margin"
         android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
         android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
         android:paddingTop="@dimen/global_actions_grid_vertical_padding"
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index 87cb5c7f..9dc502e 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -20,7 +20,7 @@
     android:id="@+id/notification_guts"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:clickable="true"
+    android:focusable="true"
     android:clipChildren="false"
     android:clipToPadding="true"
     android:orientation="vertical"
diff --git a/packages/SystemUI/res/layout/notification_guts.xml b/packages/SystemUI/res/layout/notification_guts.xml
index dc94697..5399f57 100644
--- a/packages/SystemUI/res/layout/notification_guts.xml
+++ b/packages/SystemUI/res/layout/notification_guts.xml
@@ -19,8 +19,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
+    android:focusable="true"
     android:id="@+id/notification_guts"
     android:visibility="gone"
-    android:clickable="true"
     android:gravity="top|start"
     android:theme="@*android:style/Theme.DeviceDefault.Light"/>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 73b711d..5b36382 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -20,7 +20,7 @@
     android:id="@+id/notification_guts"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:clickable="true"
+    android:focusable="true"
     android:clipChildren="false"
     android:clipToPadding="true"
     android:orientation="vertical"
diff --git a/packages/SystemUI/res/layout/pip_dismiss_view.xml b/packages/SystemUI/res/layout/pip_dismiss_view.xml
deleted file mode 100644
index 2cc4b22..0000000
--- a/packages/SystemUI/res/layout/pip_dismiss_view.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/pip_dismiss_gradient_height"
-    android:alpha="0">
-
-    <TextView
-        android:id="@+id/pip_dismiss_text"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="bottom|center_horizontal"
-        android:text="@string/pip_phone_dismiss_hint"
-        android:textColor="#FFFFFFFF"
-        android:textSize="14sp"
-        android:shadowColor="@android:color/black"
-        android:shadowDx="-2"
-        android:shadowDy="2"
-        android:shadowRadius="0.01" />
-
-</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/qs_media_panel.xml
index fc3bf94..e5ac5f8 100644
--- a/packages/SystemUI/res/layout/qs_media_panel.xml
+++ b/packages/SystemUI/res/layout/qs_media_panel.xml
@@ -23,7 +23,8 @@
     android:layout_height="match_parent"
     android:orientation="vertical"
     android:gravity="center_horizontal|fill_vertical"
-    android:padding="16dp"
+    android:paddingTop="@dimen/qs_media_panel_outer_padding"
+    android:paddingBottom="@dimen/qs_media_panel_outer_padding"
     android:background="@drawable/qs_media_background"
     >
 
@@ -42,7 +43,9 @@
             android:orientation="horizontal"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginBottom="16dp"
+            android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+            android:paddingStart="@dimen/qs_media_panel_outer_padding"
+            android:paddingEnd="16dp"
         >
 
             <ImageView
@@ -139,6 +142,7 @@
         <!-- Seek Bar -->
         <SeekBar
             android:id="@+id/media_progress_bar"
+            style="@android:style/Widget.ProgressBar.Horizontal"
             android:clickable="true"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
@@ -154,6 +158,9 @@
             android:id="@+id/notification_media_progress_time"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:paddingStart="@dimen/qs_media_panel_outer_padding"
+            android:paddingEnd="@dimen/qs_media_panel_outer_padding"
+            android:layout_marginBottom="10dp"
             android:layout_gravity="center"
             >
             <!-- width is set to "match_parent" to avoid extra layout calls -->
@@ -184,6 +191,8 @@
             android:layoutDirection="ltr"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:paddingStart="@dimen/qs_media_panel_outer_padding"
+            android:paddingEnd="@dimen/qs_media_panel_outer_padding"
             android:gravity="center"
             >
             <ImageButton
diff --git a/packages/SystemUI/res/values-sw320dp/dimens.xml b/packages/SystemUI/res/values-sw320dp/dimens.xml
index 47a2a09..c110113 100644
--- a/packages/SystemUI/res/values-sw320dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw320dp/dimens.xml
@@ -31,6 +31,6 @@
     <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen>
 
     <!-- Home Controls -->
-    <dimen name="controls_list_side_margin">10dp</dimen>
+    <dimen name="global_actions_side_margin">10dp</dimen>
 </resources>
 
diff --git a/packages/SystemUI/res/values-sw360dp/dimens.xml b/packages/SystemUI/res/values-sw360dp/dimens.xml
index 35a6536..fc510bf 100644
--- a/packages/SystemUI/res/values-sw360dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw360dp/dimens.xml
@@ -27,6 +27,6 @@
     <dimen name="navigation_side_padding">40dip</dimen>
 
     <!-- Home Controls -->
-    <dimen name="controls_list_side_margin">12dp</dimen>
+    <dimen name="global_actions_side_margin">12dp</dimen>
 </resources>
 
diff --git a/packages/SystemUI/res/values-sw392dp/dimens.xml b/packages/SystemUI/res/values-sw392dp/dimens.xml
index 308bc69..4c9d02e 100644
--- a/packages/SystemUI/res/values-sw392dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw392dp/dimens.xml
@@ -31,6 +31,6 @@
     <dimen name="global_actions_grid_item_icon_bottom_margin">4dp</dimen>
 
     <!-- Home Controls -->
-    <dimen name="controls_list_side_margin">16dp</dimen>
+    <dimen name="global_actions_side_margin">16dp</dimen>
 </resources>
 
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 288487a..4482cda 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -50,6 +50,10 @@
     <!-- The color of the text in the Global Actions menu -->
     <color name="global_actions_alert_text">@color/GM2_red_700</color>
 
+    <!-- The color of the background of the emergency button when home controls are visible -->
+    <color name="global_actions_emergency_background">@color/GM2_red_400</color>
+    <color name="global_actions_emergency_text">@color/GM2_grey_100</color>
+
     <!-- Tint color for the content on the notification overflow card. -->
     <color name="keyguard_overflow_content_color">#ff686868</color>
 
@@ -211,6 +215,7 @@
     <color name="GM2_red_50">#FCE8E6</color>
     <color name="GM2_red_200">#F6AEA9</color>
     <color name="GM2_red_300">#F28B82</color>
+    <color name="GM2_red_400">#EE675C</color>
     <color name="GM2_red_500">#B71C1C</color>
     <color name="GM2_red_700">#C5221F</color>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9b9fbed..6a8a4b9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -955,7 +955,7 @@
     <dimen name="recents_quick_scrub_onboarding_margin_start">8dp</dimen>
 
     <!-- The height of the gradient indicating the dismiss edge when moving a PIP. -->
-    <dimen name="pip_dismiss_gradient_height">176dp</dimen>
+    <dimen name="floating_dismiss_gradient_height">176dp</dimen>
 
     <!-- The bottom margin of the PIP drag to dismiss info text shown when moving a PIP. -->
     <dimen name="pip_dismiss_text_bottom_margin">24dp</dimen>
@@ -1008,6 +1008,9 @@
     <dimen name="global_actions_grid_container_shadow_offset">20dp</dimen>
     <dimen name="global_actions_grid_container_negative_shadow_offset">-20dp</dimen>
 
+    <!-- Margins at the left and right of the power menu and home controls widgets. -->
+    <dimen name="global_actions_side_margin">16dp</dimen>
+
     <!-- The maximum offset in either direction that elements are moved horizontally to prevent
          burn-in on AOD. -->
     <dimen name="burn_in_prevention_offset_x">8dp</dimen>
@@ -1211,6 +1214,7 @@
     <!-- Size of media cards in the QSPanel carousel -->
     <dimen name="qs_media_width">350dp</dimen>
     <dimen name="qs_media_padding">8dp</dimen>
+    <dimen name="qs_media_panel_outer_padding">16dp</dimen>
     <dimen name="qs_media_corner_radius">10dp</dimen>
     <dimen name="qs_media_album_size">72dp</dimen>
     <dimen name="qs_seamless_icon_size">20dp</dimen>
@@ -1231,7 +1235,6 @@
     <dimen name="controls_header_side_margin">4dp</dimen>
     <dimen name="controls_header_menu_size">48dp</dimen>
     <dimen name="controls_header_app_icon_size">40dp</dimen>
-    <dimen name="controls_list_side_margin">16dp</dimen>
     <dimen name="controls_top_margin">44dp</dimen>
     <dimen name="control_header_text_size">22sp</dimen>
     <dimen name="control_text_size">14sp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index aabee1c..3e02b30 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -511,7 +511,7 @@
     </style>
 
     <style name="TextAppearance.NotificationInfo">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
         <item name="android:textColor">@color/notification_primary_text_color</item>
     </style>
 
@@ -521,7 +521,6 @@
     </style>
 
     <style name="TextAppearance.NotificationInfo.Title">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
         <item name="android:textColor">@color/notification_primary_text_color</item>
         <item name="android:textStyle">bold</item>
     </style>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index 3bda3c8..806678f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -261,6 +261,11 @@
                         animationHandler.onAnimationCanceled(
                                 taskSnapshot != null ? new ThumbnailData(taskSnapshot) : null);
                     }
+
+                    @Override
+                    public void onTaskAppeared(RemoteAnimationTarget app) {
+                        animationHandler.onTaskAppeared(new RemoteAnimationTargetCompat(app));
+                    }
                 };
             }
             ActivityTaskManager.getService().startRecentsActivity(intent, receiver, runner);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index bbb83c7..76513c6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -109,4 +109,16 @@
             Log.e(TAG, "Failed to set overview reached state", e);
         }
     }
+
+    /**
+     * @see IRecentsAnimationController#removeTask
+     */
+    public boolean removeTask(int taskId) {
+        try {
+            return mAnimationController.removeTask(taskId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to remove remote animation target", e);
+            return false;
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
index 2c99c5c..c4cd192 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java
@@ -32,4 +32,10 @@
      * Called when the animation into Recents was canceled. This call is made on the binder thread.
      */
     void onAnimationCanceled(ThumbnailData thumbnailData);
+
+    /**
+     * Called when the task of an activity that has been started while the recents animation
+     * was running becomes ready for control.
+     */
+    void onTaskAppeared(RemoteAnimationTargetCompat app);
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
index 7eb5a8f..e99245f 100644
--- a/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AdminSecondaryLockScreenController.java
@@ -34,6 +34,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.NoSuchElementException;
+
 /**
  * Encapsulates all logic for secondary lockscreen state management.
  */
@@ -79,7 +81,9 @@
     private final IKeyguardCallback mCallback = new IKeyguardCallback.Stub() {
         @Override
         public void onDismiss() {
-            dismiss(UserHandle.getCallingUserId());
+            mHandler.post(() -> {
+                dismiss(UserHandle.getCallingUserId());
+            });
         }
 
         @Override
@@ -91,7 +95,9 @@
             if (surfacePackage != null) {
                 mView.setChildSurfacePackage(surfacePackage);
             } else {
-                dismiss(KeyguardUpdateMonitor.getCurrentUser());
+                mHandler.post(() -> {
+                    dismiss(KeyguardUpdateMonitor.getCurrentUser());
+                });
             }
         }
     };
@@ -122,6 +128,7 @@
                         // If the remote content is not readied within the timeout period,
                         // move on without the secondary lockscreen.
                         dismiss(userId);
+                        Log.w(TAG, "Timed out waiting for secondary lockscreen content.");
                     },
                     REMOTE_CONTENT_READY_TIMEOUT_MILLIS);
         }
@@ -150,8 +157,12 @@
      * Displays the Admin security Surface view.
      */
     public void show(Intent serviceIntent) {
-        mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
-        mParent.addView(mView);
+        if (mClient == null) {
+            mContext.bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
+        }
+        if (!mView.isAttachedToWindow()) {
+            mParent.addView(mView);
+        }
     }
 
     /**
@@ -162,7 +173,11 @@
             mParent.removeView(mView);
         }
         if (mClient != null) {
-            mClient.asBinder().unlinkToDeath(mKeyguardClientDeathRecipient, 0);
+            try {
+                mClient.asBinder().unlinkToDeath(mKeyguardClientDeathRecipient, 0);
+            } catch (NoSuchElementException e) {
+                Log.w(TAG, "IKeyguardClient death recipient already released");
+            }
             mContext.unbindService(mConnection);
             mClient = null;
         }
@@ -185,10 +200,12 @@
 
     private void dismiss(int userId) {
         mHandler.removeCallbacksAndMessages(null);
-        if (mView != null && mView.isAttachedToWindow()
-                && userId == KeyguardUpdateMonitor.getCurrentUser()) {
+        if (mView.isAttachedToWindow() && userId == KeyguardUpdateMonitor.getCurrentUser()) {
             hide();
-            mKeyguardCallback.dismiss(true, userId);
+            if (mKeyguardCallback != null) {
+                mKeyguardCallback.dismiss(/* securityVerified= */ true, userId,
+                        /* bypassSecondaryLockScreen= */true);
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index d5a08dd..aa2fe3c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -85,7 +85,8 @@
                         // the user proved presence via some other way to the trust agent.
                         Log.i(TAG, "TrustAgent dismissed Keyguard.");
                     }
-                    dismiss(false /* authenticated */, userId);
+                    dismiss(false /* authenticated */, userId,
+                            /* bypassSecondaryLockScreen */ false);
                 } else {
                     mViewMediatorCallback.playTrustedSound();
                 }
@@ -190,7 +191,7 @@
      * @return True if the keyguard is done.
      */
     public boolean dismiss(int targetUserId) {
-        return dismiss(false, targetUserId);
+        return dismiss(false, targetUserId, false);
     }
 
     public boolean handleBackKey() {
@@ -206,8 +207,10 @@
     }
 
     @Override
-    public boolean dismiss(boolean authenticated, int targetUserId) {
-        return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId);
+    public boolean dismiss(boolean authenticated, int targetUserId,
+            boolean bypassSecondaryLockScreen) {
+        return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId,
+                bypassSecondaryLockScreen);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
index d154434..af5196f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java
@@ -24,6 +24,8 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
 import android.util.Log;
 import android.view.View;
 import android.widget.ImageButton;
@@ -40,6 +42,7 @@
 import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.media.MediaControllerFactory;
 import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.stack.MediaHeaderView;
@@ -71,10 +74,11 @@
     private KeyguardMediaObserver mObserver;
 
     @Inject
-    public KeyguardMediaPlayer(Context context, @Background Executor backgroundExecutor) {
+    public KeyguardMediaPlayer(Context context, MediaControllerFactory factory,
+            @Background Executor backgroundExecutor) {
         mContext = context;
         mBackgroundExecutor = backgroundExecutor;
-        mViewModel = new KeyguardMediaViewModel(context);
+        mViewModel = new KeyguardMediaViewModel(context, factory);
     }
 
     /** Binds media controls to a view hierarchy. */
@@ -139,14 +143,16 @@
     private static final class KeyguardMediaViewModel {
 
         private final Context mContext;
+        private final MediaControllerFactory mMediaControllerFactory;
         private final MutableLiveData<KeyguardMedia> mMedia = new MutableLiveData<>();
         private final Object mActionsLock = new Object();
         private List<PendingIntent> mActions;
         private float mAlbumArtRadius;
         private int mAlbumArtSize;
 
-        KeyguardMediaViewModel(Context context) {
+        KeyguardMediaViewModel(Context context, MediaControllerFactory factory) {
             mContext = context;
+            mMediaControllerFactory = factory;
             loadDimens();
         }
 
@@ -162,6 +168,17 @@
         public void updateControls(NotificationEntry entry, Icon appIcon,
                 MediaMetadata mediaMetadata) {
 
+            // Check the playback state of the media controller. If it is null, then the session was
+            // probably destroyed. Don't update in this case.
+            final MediaSession.Token token = entry.getSbn().getNotification().extras
+                    .getParcelable(Notification.EXTRA_MEDIA_SESSION);
+            final MediaController controller = token != null
+                    ? mMediaControllerFactory.create(token) : null;
+            if (controller != null && controller.getPlaybackState() == null) {
+                clearControls();
+                return;
+            }
+
             // Foreground and Background colors computed from album art
             Notification notif = entry.getSbn().getNotification();
             int fgColor = notif.color;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
index 49dcfff..e384727 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java
@@ -25,6 +25,15 @@
     void dismiss(boolean securityVerified, int targetUserId);
 
     /**
+     * Dismiss the given security screen.
+     * @param securityVerified true if the user correctly entered credentials for the given screen.
+     * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
+     * @param bypassSecondaryLockScreen true if the user can bypass the secondary lock screen,
+     *                                  if any, during this dismissal.
+     */
+    void dismiss(boolean securityVerified, int targetUserId, boolean bypassSecondaryLockScreen);
+
+    /**
      * Manually report user activity to keep the device awake.
      */
     void userActivity();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index ba8a1a9..1e1ce4e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -115,7 +115,8 @@
 
     // Used to notify the container when something interesting happens.
     public interface SecurityCallback {
-        public boolean dismiss(boolean authenticated, int targetUserId);
+        public boolean dismiss(boolean authenticated, int targetUserId,
+                boolean bypassSecondaryLockScreen);
         public void userActivity();
         public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
 
@@ -504,9 +505,12 @@
      * @param authenticated true if the user entered the correct authentication
      * @param targetUserId a user that needs to be the foreground user at the finish (if called)
      *     completion.
+     * @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary
+     *     secondary lock screen requirement, if any.
      * @return true if keyguard is done
      */
-    boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId) {
+    boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId,
+            boolean bypassSecondaryLockScreen) {
         if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")");
         boolean finish = false;
         boolean strongAuth = false;
@@ -555,7 +559,7 @@
             }
         }
         // Check for device admin specified additional security measures.
-        if (finish) {
+        if (finish && !bypassSecondaryLockScreen) {
             Intent secondaryLockscreenIntent =
                     mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId);
             if (secondaryLockscreenIntent != null) {
@@ -636,8 +640,15 @@
             mUpdateMonitor.cancelFaceAuth();
         }
 
+        @Override
         public void dismiss(boolean authenticated, int targetId) {
-            mSecurityCallback.dismiss(authenticated, targetId);
+            dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false);
+        }
+
+        @Override
+        public void dismiss(boolean authenticated, int targetId,
+                boolean bypassSecondaryLockScreen) {
+            mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen);
         }
 
         public boolean isVerifyUnlockOnly() {
@@ -689,6 +700,9 @@
         @Override
         public void dismiss(boolean securityVerified, int targetUserId) { }
         @Override
+        public void dismiss(boolean authenticated, int targetId,
+                boolean bypassSecondaryLockScreen) { }
+        @Override
         public void onUserInput() { }
         @Override
         public void reset() {}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 88d6943..367058f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -57,7 +57,6 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
-import android.media.AudioManager;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IRemoteCallback;
@@ -82,6 +81,8 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 
+import androidx.lifecycle.Observer;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.settingslib.WirelessUtils;
@@ -97,6 +98,7 @@
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.RingerModeTracker;
 
 import com.google.android.collect.Lists;
 
@@ -258,6 +260,7 @@
     private TrustManager mTrustManager;
     private UserManager mUserManager;
     private KeyguardBypassController mKeyguardBypassController;
+    private RingerModeTracker mRingerModeTracker;
     private int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
     private int mFaceRunningState = BIOMETRIC_STATE_STOPPED;
     private LockPatternUtils mLockPatternUtils;
@@ -295,6 +298,13 @@
 
     private final Handler mHandler;
 
+    private final Observer<Integer> mRingerModeObserver = new Observer<Integer>() {
+        @Override
+        public void onChanged(Integer ringer) {
+            mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED, ringer, 0).sendToTarget();
+        }
+    };
+
     private SparseBooleanArray mFaceSettingEnabledForUser = new SparseBooleanArray();
     private BiometricManager mBiometricManager;
     private IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
@@ -1145,9 +1155,6 @@
                 }
                 mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, args.subId, args.slotId, args.simState)
                         .sendToTarget();
-            } else if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_RINGER_MODE_CHANGED,
-                        intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1), 0));
             } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
                 String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_PHONE_STATE_CHANGED, state));
@@ -1501,6 +1508,10 @@
         mUserTrustIsUsuallyManaged.delete(userId);
     }
 
+    private void registerRingerTracker() {
+        mRingerModeTracker.getRingerMode().observeForever(mRingerModeObserver);
+    }
+
     @VisibleForTesting
     @Inject
     protected KeyguardUpdateMonitor(
@@ -1508,6 +1519,7 @@
             @Main Looper mainLooper,
             BroadcastDispatcher broadcastDispatcher,
             DumpManager dumpManager,
+            RingerModeTracker ringerModeTracker,
             @Background Executor backgroundExecutor) {
         mContext = context;
         mSubscriptionManager = SubscriptionManager.from(context);
@@ -1515,6 +1527,7 @@
         mStrongAuthTracker = new StrongAuthTracker(context, this::notifyStrongAuthStateChanged);
         mBackgroundExecutor = backgroundExecutor;
         mBroadcastDispatcher = broadcastDispatcher;
+        mRingerModeTracker = ringerModeTracker;
         dumpManager.registerDumpable(getClass().getName(), this);
 
         mHandler = new Handler(mainLooper) {
@@ -1648,10 +1661,11 @@
         filter.addAction(Intent.ACTION_SERVICE_STATE);
         filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
         filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
-        filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
         mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, mHandler);
 
+        mHandler.post(this::registerRingerTracker);
+
         final IntentFilter allUserFilter = new IntentFilter();
         allUserFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
         allUserFilter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
@@ -2802,6 +2816,7 @@
 
         mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver);
         mBroadcastDispatcher.unregisterReceiver(mBroadcastAllReceiver);
+        mRingerModeTracker.getRingerMode().removeObserver(mRingerModeObserver);
 
         mHandler.removeCallbacksAndMessages(null);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
index 362014f..e17d4e6 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender.java
@@ -25,6 +25,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.statusbar.NotificationLifetimeExtender;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.time.SystemClock;
 
 /**
  * Extends the lifetime of foreground notification services such that they show for at least
@@ -39,8 +40,10 @@
     private NotificationSafeToRemoveCallback mNotificationSafeToRemoveCallback;
     private ArraySet<NotificationEntry> mManagedEntries = new ArraySet<>();
     private Handler mHandler = new Handler(Looper.getMainLooper());
+    private final SystemClock mSystemClock;
 
-    public ForegroundServiceLifetimeExtender() {
+    public ForegroundServiceLifetimeExtender(SystemClock systemClock) {
+        mSystemClock = systemClock;
     }
 
     @Override
@@ -55,8 +58,8 @@
             return false;
         }
 
-        long currentTime = System.currentTimeMillis();
-        return currentTime - entry.getSbn().getPostTime() < MIN_FGS_TIME_MS;
+        long currentTime = mSystemClock.uptimeMillis();
+        return currentTime - entry.getCreationTime() < MIN_FGS_TIME_MS;
     }
 
     @Override
@@ -84,7 +87,7 @@
             }
         };
         long delayAmt = MIN_FGS_TIME_MS
-                - (System.currentTimeMillis() - entry.getSbn().getPostTime());
+                - (mSystemClock.uptimeMillis() - entry.getCreationTime());
         mHandler.postDelayed(r, delayAmt);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index f01fa81..ef1f4e0 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.util.time.SystemClock;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -49,7 +50,8 @@
     public ForegroundServiceNotificationListener(Context context,
             ForegroundServiceController foregroundServiceController,
             NotificationEntryManager notificationEntryManager,
-            NotifPipeline notifPipeline) {
+            NotifPipeline notifPipeline,
+            SystemClock systemClock) {
         mContext = context;
         mForegroundServiceController = foregroundServiceController;
 
@@ -76,7 +78,8 @@
                 removeNotification(entry.getSbn());
             }
         });
-        mEntryManager.addNotificationLifetimeExtender(new ForegroundServiceLifetimeExtender());
+        mEntryManager.addNotificationLifetimeExtender(
+                new ForegroundServiceLifetimeExtender(systemClock));
 
         notifPipeline.addCollectionListener(new NotifCollectionListener() {
             @Override
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 23fa645..5442299 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -120,7 +120,7 @@
         private void init(DozeParameters dozeParameters) {
             mIsHighEndGfx = ActivityManager.isHighEndGfx();
             mDisplayNeedsBlanking = dozeParameters.getDisplayNeedsBlanking();
-            mNeedTransition = mIsHighEndGfx && !mDisplayNeedsBlanking;
+            mNeedTransition = false;
 
             // We will preserve EGL context when we are in lock screen or aod
             // to avoid janking in following transition, we need to release when back to home.
@@ -137,7 +137,7 @@
             mRenderer = getRendererInstance();
             getDisplayContext().getDisplay().getDisplayInfo(mDisplayInfo);
             setFixedSizeAllowed(true);
-            setOffsetNotificationsEnabled(true);
+            setOffsetNotificationsEnabled(mNeedTransition);
             updateSurfaceSize();
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java
index 6923079..13d6a9b 100644
--- a/packages/SystemUI/src/com/android/systemui/Interpolators.java
+++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java
@@ -49,6 +49,8 @@
     public static final Interpolator CUSTOM_40_40 = new PathInterpolator(0.4f, 0f, 0.6f, 1f);
     public static final Interpolator HEADS_UP_APPEAR = new HeadsUpAppearInterpolator();
     public static final Interpolator ICON_OVERSHOT = new PathInterpolator(0.4f, 0f, 0.2f, 1.4f);
+    public static final Interpolator SHADE_ANIMATION =
+            new PathInterpolator(0.6f, 0.02f, 0.4f, 0.98f);
     public static final Interpolator ICON_OVERSHOT_LESS
             = new PathInterpolator(0.4f, 0f, 0.2f, 1.1f);
     public static final Interpolator PANEL_CLOSE_ACCELERATED
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 9d885fd..669a86b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -599,7 +599,7 @@
         if (mStackView == null) {
             mStackView = new BubbleStackView(
                     mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
-                    mSysUiState);
+                    mSysUiState, mNotificationShadeWindowController);
             ViewGroup nsv = mNotificationShadeWindowController.getNotificationShadeView();
             int bubbleScrimIndex = nsv.indexOfChild(nsv.findViewById(R.id.scrim_for_bubble));
             int stackIndex = bubbleScrimIndex + 1;  // Show stack above bubble scrim.
@@ -719,13 +719,6 @@
     }
 
     /**
-     * Tell the stack of bubbles to expand.
-     */
-    public void expandStack() {
-        mBubbleData.setExpanded(true);
-    }
-
-    /**
      * Tell the stack of bubbles to collapse.
      */
     public void collapseStack() {
@@ -753,12 +746,6 @@
         return (isSummary && isSuppressedSummary) || isBubbleAndSuppressed;
     }
 
-    @VisibleForTesting
-    void selectBubble(String key) {
-        Bubble bubble = mBubbleData.getBubbleWithKey(key);
-        mBubbleData.setSelectedBubble(bubble);
-    }
-
     void promoteBubbleFromOverflow(Bubble bubble) {
         bubble.setInflateSynchronously(mInflateSynchronously);
         mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
@@ -778,13 +765,6 @@
     }
 
     /**
-     * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack.
-     */
-    void dismissStack(@DismissReason int reason) {
-        mBubbleData.dismissAll(reason);
-    }
-
-    /**
      * Directs a back gesture at the bubble stack. When opened, the current expanded bubble
      * is forwarded a back key down/up pair.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index be9cd5f..4c149dd 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -28,6 +28,7 @@
 import android.service.notification.NotificationListenerService;
 import android.util.Log;
 import android.util.Pair;
+import android.view.View;
 
 import androidx.annotation.Nullable;
 
@@ -751,6 +752,7 @@
     }
 
     @VisibleForTesting(visibility = PRIVATE)
+    @Nullable
     Bubble getBubbleWithKey(String key) {
         for (int i = 0; i < mBubbles.size(); i++) {
             Bubble bubble = mBubbles.get(i);
@@ -761,6 +763,17 @@
         return null;
     }
 
+    @Nullable
+    Bubble getBubbleWithView(View view) {
+        for (int i = 0; i < mBubbles.size(); i++) {
+            Bubble bubble = mBubbles.get(i);
+            if (bubble.getIconView() != null && bubble.getIconView().equals(view)) {
+                return bubble;
+            }
+        }
+        return null;
+    }
+
     @VisibleForTesting(visibility = PRIVATE)
     Bubble getOverflowBubbleWithKey(String key) {
         for (int i = 0; i < mOverflowBubbles.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 496456d..93fb697 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.bubbles;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.view.Display.INVALID_DISPLAY;
@@ -126,6 +127,7 @@
                     ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
                             0 /* enterResId */, 0 /* exitResId */);
                     options.setTaskAlwaysOnTop(true);
+                    options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
                     // Post to keep the lifecycle normal
                     post(() -> {
                         if (DEBUG_BUBBLE_EXPANDED_VIEW) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index eff6934..61e6f39 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -30,6 +30,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
 import android.app.Notification;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -43,6 +44,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.Region;
 import android.os.Bundle;
 import android.os.Vibrator;
 import android.util.Log;
@@ -81,8 +83,10 @@
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.util.DismissCircleView;
 import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.systemui.util.RelativeTouchListener;
 import com.android.systemui.util.animation.PhysicsAnimator;
 import com.android.systemui.util.magnetictarget.MagnetizedObject;
 
@@ -239,7 +243,6 @@
         mExpandedAnimationController.dump(fd, pw, args);
     }
 
-    private BubbleTouchHandler mTouchHandler;
     private BubbleController.BubbleExpandListener mExpandListener;
     private SysUiState mSysUiState;
 
@@ -296,7 +299,7 @@
 
                 @Override
                 public void setValue(Object o, float v) {
-                    onFlyoutDragged(v);
+                    setFlyoutStateForDragLength(v);
                 }
             };
 
@@ -328,6 +331,8 @@
     @NonNull
     private final SurfaceSynchronizer mSurfaceSynchronizer;
 
+    private final NotificationShadeWindowController mNotificationShadeWindowController;
+
     /**
      * The currently magnetized object, which is being dragged and will be attracted to the magnetic
      * dismiss target.
@@ -337,13 +342,6 @@
     private MagnetizedObject<?> mMagnetizedObject;
 
     /**
-     * The action to run when the magnetized object is released in the dismiss target.
-     *
-     * This will actually perform the dismissal of either the stack or an individual bubble.
-     */
-    private Runnable mReleasedInDismissTargetAction;
-
-    /**
      * The MagneticTarget instance for our circular dismiss view. This is added to the
      * MagnetizedObject instances for the stack and any dragged-out bubbles.
      */
@@ -377,7 +375,7 @@
                 public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
                     mExpandedAnimationController.dismissDraggedOutBubble(
                             mExpandedAnimationController.getDraggedOutBubble(),
-                            mReleasedInDismissTargetAction);
+                            BubbleStackView.this::dismissMagnetizedObject);
                     hideDismissTarget();
                 }
             };
@@ -410,7 +408,7 @@
                     mStackAnimationController.implodeStack(
                             () -> {
                                 resetDesaturationAndDarken();
-                                mReleasedInDismissTargetAction.run();
+                                dismissMagnetizedObject();
                             }
                     );
 
@@ -418,6 +416,197 @@
                 }
             };
 
+    /**
+     * Click listener set on each bubble view. When collapsed, clicking a bubble expands the stack.
+     * When expanded, clicking a bubble either expands that bubble, or collapses the stack.
+     */
+    private OnClickListener mBubbleClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            final Bubble clickedBubble = mBubbleData.getBubbleWithView(view);
+
+            // If the bubble has since left us, ignore the click.
+            if (clickedBubble == null) {
+                return;
+            }
+
+            final boolean clickedBubbleIsCurrentlyExpandedBubble =
+                    clickedBubble.getKey().equals(mExpandedBubble.getKey());
+
+            if (isExpanded() && !clickedBubbleIsCurrentlyExpandedBubble) {
+                if (clickedBubble != mBubbleData.getSelectedBubble()) {
+                    // Select the clicked bubble.
+                    mBubbleData.setSelectedBubble(clickedBubble);
+                } else {
+                    // If the clicked bubble is the selected bubble (but not the expanded bubble),
+                    // that means overflow was previously expanded. Set the selected bubble
+                    // internally without going through BubbleData (which would ignore it since it's
+                    // already selected).
+                    setSelectedBubble(clickedBubble);
+
+                }
+            } else {
+                // Otherwise, we either tapped the stack (which means we're collapsed
+                // and should expand) or the currently selected bubble (we're expanded
+                // and should collapse).
+                if (!maybeShowStackUserEducation()) {
+                    mBubbleData.setExpanded(!mBubbleData.isExpanded());
+                }
+            }
+        }
+    };
+
+    /**
+     * Touch listener set on each bubble view. This enables dragging and dismissing the stack (when
+     * collapsed), or individual bubbles (when expanded).
+     */
+    private RelativeTouchListener mBubbleTouchListener = new RelativeTouchListener() {
+
+        @Override
+        public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
+            // If we're expanding or collapsing, consume but ignore all touch events.
+            if (mIsExpansionAnimating) {
+                return true;
+            }
+
+            if (mBubbleData.isExpanded()) {
+                maybeShowManageEducation(false /* show */);
+
+                // If we're expanded, tell the animation controller to prepare to drag this bubble,
+                // dispatching to the individual bubble magnet listener.
+                mExpandedAnimationController.prepareForBubbleDrag(
+                        v /* bubble */,
+                        mMagneticTarget,
+                        mIndividualBubbleMagnetListener);
+
+                // Save the magnetized individual bubble so we can dispatch touch events to it.
+                mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut();
+            } else {
+                // If we're collapsed, prepare to drag the stack. Cancel active animations, set the
+                // animation controller, and hide the flyout.
+                mStackAnimationController.cancelStackPositionAnimations();
+                mBubbleContainer.setActiveController(mStackAnimationController);
+                hideFlyoutImmediate();
+
+                // Also, save the magnetized stack so we can dispatch touch events to it.
+                mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
+                mMagnetizedObject.setMagnetListener(mStackMagnetListener);
+            }
+
+            passEventToMagnetizedObject(ev);
+
+            // Bubbles are always interested in all touch events!
+            return true;
+        }
+
+        @Override
+        public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+                float viewInitialY, float dx, float dy) {
+            // If we're expanding or collapsing, ignore all touch events.
+            if (mIsExpansionAnimating) {
+                return;
+            }
+
+            // Show the dismiss target, if we haven't already.
+            springInDismissTargetMaybe();
+
+            // First, see if the magnetized object consumes the event - if so, we shouldn't move the
+            // bubble since it's stuck to the target.
+            if (!passEventToMagnetizedObject(ev)) {
+                if (mBubbleData.isExpanded()) {
+                    mExpandedAnimationController.dragBubbleOut(
+                            v, viewInitialX + dx, viewInitialY + dy);
+                } else {
+                    hideStackUserEducation(false /* fromExpansion */);
+                    mStackAnimationController.moveStackFromTouch(
+                            viewInitialX + dx, viewInitialY + dy);
+                }
+            }
+        }
+
+        @Override
+        public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+                float viewInitialY, float dx, float dy, float velX, float velY) {
+            // If we're expanding or collapsing, ignore all touch events.
+            if (mIsExpansionAnimating) {
+                return;
+            }
+
+            // First, see if the magnetized object consumes the event - if so, the bubble was
+            // released in the target or flung out of it, and we should ignore the event.
+            if (!passEventToMagnetizedObject(ev)) {
+                if (mBubbleData.isExpanded()) {
+                    mExpandedAnimationController.snapBubbleBack(v, velX, velY);
+                } else {
+                    // Fling the stack to the edge, and save whether or not it's going to end up on
+                    // the left side of the screen.
+                    mStackOnLeftOrWillBe =
+                            mStackAnimationController.flingStackThenSpringToEdge(
+                                    viewInitialX + dx, velX, velY) <= 0;
+
+                    updateBubbleZOrdersAndDotPosition(true /* animate */);
+
+                    logBubbleEvent(null /* no bubble associated with bubble stack move */,
+                            SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
+                }
+
+                hideDismissTarget();
+            }
+        }
+    };
+
+    /** Click listener set on the flyout, which expands the stack when the flyout is tapped. */
+    private OnClickListener mFlyoutClickListener = new OnClickListener() {
+        @Override
+        public void onClick(View view) {
+            if (maybeShowStackUserEducation()) {
+                // If we're showing user education, don't open the bubble show the education first
+                mBubbleToExpandAfterFlyoutCollapse = null;
+            } else {
+                mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
+            }
+
+            mFlyout.removeCallbacks(mHideFlyout);
+            mHideFlyout.run();
+        }
+    };
+
+    /** Touch listener for the flyout. This enables the drag-to-dismiss gesture on the flyout. */
+    private RelativeTouchListener mFlyoutTouchListener = new RelativeTouchListener() {
+
+        @Override
+        public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
+            mFlyout.removeCallbacks(mHideFlyout);
+            return true;
+        }
+
+        @Override
+        public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+                float viewInitialY, float dx, float dy) {
+            setFlyoutStateForDragLength(dx);
+        }
+
+        @Override
+        public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+                float viewInitialY, float dx, float dy, float velX, float velY) {
+            final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
+            final boolean metRequiredVelocity =
+                    onLeft ? velX < -FLYOUT_DISMISS_VELOCITY : velX > FLYOUT_DISMISS_VELOCITY;
+            final boolean metRequiredDeltaX =
+                    onLeft
+                            ? dx < -mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS
+                            : dx > mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS;
+            final boolean isCancelFling = onLeft ? velX > 0 : velX < 0;
+            final boolean shouldDismiss = metRequiredVelocity
+                    || (metRequiredDeltaX && !isCancelFling);
+
+            mFlyout.removeCallbacks(mHideFlyout);
+            animateFlyoutCollapsed(shouldDismiss, velX);
+
+            maybeShowStackUserEducation();
+        }
+    };
+
     private ViewGroup mDismissTargetContainer;
     private PhysicsAnimator<View> mDismissTargetAnimator;
     private PhysicsAnimator.SpringConfig mDismissTargetSpring = new PhysicsAnimator.SpringConfig(
@@ -436,18 +625,19 @@
     private BubbleManageEducationView mManageEducationView;
     private boolean mAnimatingManageEducationAway;
 
+    @SuppressLint("ClickableViewAccessibility")
     public BubbleStackView(Context context, BubbleData data,
             @Nullable SurfaceSynchronizer synchronizer,
             FloatingContentCoordinator floatingContentCoordinator,
-            SysUiState sysUiState) {
+            SysUiState sysUiState,
+            NotificationShadeWindowController notificationShadeWindowController) {
         super(context);
 
         mBubbleData = data;
         mInflater = LayoutInflater.from(context);
-        mTouchHandler = new BubbleTouchHandler(this, data, context);
-        setOnTouchListener(mTouchHandler);
 
         mSysUiState = sysUiState;
+        mNotificationShadeWindowController = notificationShadeWindowController;
 
         Resources res = getResources();
         mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
@@ -514,7 +704,7 @@
         mDismissTargetContainer = new FrameLayout(context);
         mDismissTargetContainer.setLayoutParams(new FrameLayout.LayoutParams(
                 MATCH_PARENT,
-                getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height),
+                getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
                 Gravity.BOTTOM));
         mDismissTargetContainer.setClipChildren(false);
         mDismissTargetContainer.addView(targetView);
@@ -523,7 +713,7 @@
 
         // Start translated down so the target springs up.
         targetView.setTranslationY(
-                getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height));
+                getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height));
 
         // Save the MagneticTarget instance for the newly set up view - we'll add this to the
         // MagnetizedObjects.
@@ -641,6 +831,18 @@
             mDesaturateAndDarkenPaint.setColorFilter(new ColorMatrixColorFilter(animatedMatrix));
             mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint);
         });
+
+        // If the stack itself is touched, it means none of its touchable views (bubbles, flyouts,
+        // ActivityViews, etc.) were touched. Collapse the stack if it's expanded.
+        setOnTouchListener((view, ev) -> {
+            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                if (mBubbleData.isExpanded()) {
+                    mBubbleData.setExpanded(false);
+                }
+            }
+
+            return false;
+        });
     }
 
     private void setUpUserEducation() {
@@ -690,6 +892,7 @@
         }
     }
 
+    @SuppressLint("ClickableViewAccessibility")
     private void setUpFlyout() {
         if (mFlyout != null) {
             removeView(mFlyout);
@@ -699,6 +902,8 @@
         mFlyout.animate()
                 .setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
                 .setInterpolator(new AccelerateDecelerateInterpolator());
+        mFlyout.setOnClickListener(mFlyoutClickListener);
+        mFlyout.setOnTouchListener(mFlyoutTouchListener);
         addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
     }
 
@@ -718,6 +923,7 @@
         mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex,
                 new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
 
+        mBubbleOverflow.getBtn().setOnClickListener(v -> setSelectedBubble(mBubbleOverflow));
     }
     /**
      * Handle theme changes.
@@ -920,6 +1126,7 @@
     }
 
     // via BubbleData.Listener
+    @SuppressLint("ClickableViewAccessibility")
     void addBubble(Bubble bubble) {
         if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "addBubble: " + bubble);
@@ -944,6 +1151,9 @@
         bubble.getIconView().setDotPositionOnLeft(
                 !mStackOnLeftOrWillBe /* onLeft */, false /* animate */);
 
+        bubble.getIconView().setOnClickListener(mBubbleClickListener);
+        bubble.getIconView().setOnTouchListener(mBubbleTouchListener);
+
         mBubbleContainer.addView(bubble.getIconView(), 0,
                 new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
         ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters);
@@ -1009,10 +1219,6 @@
         updatePointerPosition();
     }
 
-    void showOverflow() {
-        setSelectedBubble(mBubbleOverflow);
-    }
-
     /**
      * Changes the currently selected bubble. If the stack is already expanded, the newly selected
      * bubble will be shown immediately. This does not change the expanded state or change the
@@ -1177,14 +1383,6 @@
         }
     }
 
-    /*
-     * Sets the action to run to dismiss the currently dragging object (either the stack or an
-     * individual bubble).
-     */
-    public void setReleasedInDismissTargetAction(Runnable action) {
-        mReleasedInDismissTargetAction = action;
-    }
-
     /**
      * Dismiss the stack of bubbles.
      *
@@ -1201,54 +1399,6 @@
     }
 
     /**
-     * @return the view the touch event is on
-     */
-    @Nullable
-    public View getTargetView(MotionEvent event) {
-        float x = event.getRawX();
-        float y = event.getRawY();
-        if (mIsExpanded) {
-            if (isIntersecting(mBubbleContainer, x, y)) {
-                if (BubbleExperimentConfig.allowBubbleOverflow(mContext)
-                        && isIntersecting(mBubbleOverflow.getBtn(), x, y)) {
-                    return mBubbleOverflow.getBtn();
-                }
-                // Could be tapping or dragging a bubble while expanded
-                for (int i = 0; i < getBubbleCount(); i++) {
-                    BadgedImageView view = (BadgedImageView) mBubbleContainer.getChildAt(i);
-                    if (isIntersecting(view, x, y)) {
-                        return view;
-                    }
-                }
-            }
-            BubbleExpandedView bev = (BubbleExpandedView) mExpandedViewContainer.getChildAt(0);
-            if (bev.intersectingTouchableContent((int) x, (int) y)) {
-                return bev;
-            }
-            // Outside of the parts we care about.
-            return null;
-        } else if (mFlyout.getVisibility() == VISIBLE && isIntersecting(mFlyout, x, y)) {
-            return mFlyout;
-        } else if (mUserEducationView != null && mUserEducationView.getVisibility() == VISIBLE) {
-            View bubbleChild = mBubbleContainer.getChildAt(0);
-            if (isIntersecting(bubbleChild, x, y)) {
-                return this;
-            } else if (isIntersecting(mUserEducationView, x, y)) {
-                return mUserEducationView;
-            } else {
-                return null;
-            }
-        }
-
-        // If it wasn't an individual bubble in the expanded state, or the flyout, it's the stack.
-        return this;
-    }
-
-    View getFlyoutView() {
-        return mFlyout;
-    }
-
-    /**
      * @deprecated use {@link #setExpanded(boolean)} and
      * {@link BubbleData#setSelectedBubble(Bubble)}
      */
@@ -1385,124 +1535,74 @@
         }
     }
 
-    /** Called when the collapsed stack is tapped on. */
-    void onStackTapped() {
-        if (!maybeShowStackUserEducation()) {
-            mBubbleData.setExpanded(true);
+    /**
+     * This method is called by {@link android.app.ActivityView} because the BubbleStackView has a
+     * higher Z-index than the ActivityView (so that dragged-out bubbles are visible over the AV).
+     * ActivityView is asking BubbleStackView to subtract the stack's bounds from the provided
+     * touchable region, so that the ActivityView doesn't consume events meant for the stack. Due to
+     * the special nature of ActivityView, it does not respect the standard
+     * {@link #dispatchTouchEvent} and {@link #onInterceptTouchEvent} methods typically used for
+     * this purpose.
+     *
+     * BubbleStackView is MATCH_PARENT, so that bubbles can be positioned via their translation
+     * properties for performance reasons. This means that the default implementation of this method
+     * subtracts the entirety of the screen from the ActivityView's touchable region, resulting in
+     * it not receiving any touch events. This was previously addressed by returning false in the
+     * stack's {@link View#canReceivePointerEvents()} method, but this precluded the use of any
+     * touch handlers in the stack or its child views.
+     *
+     * To support touch handlers, we're overriding this method to leave the ActivityView's touchable
+     * region alone. The only touchable part of the stack that can ever overlap the AV is a
+     * dragged-out bubble that is animating back into the row of bubbles. It's not worth continually
+     * updating the touchable region to allow users to grab a bubble while it completes its ~50ms
+     * animation back to the bubble row.
+     *
+     * NOTE: Any future additions to the stack that obscure the ActivityView region will need their
+     * bounds subtracted here in order to receive touch events.
+     */
+    @Override
+    public void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
+        // If the notification shade is expanded, we shouldn't let the ActivityView steal any touch
+        // events from any location.
+        if (mNotificationShadeWindowController.getPanelExpanded()) {
+            touchableRegion.setEmpty();
         }
     }
 
-    /** Called when a drag operation on an individual bubble has started. */
-    public void onBubbleDragStart(View bubble) {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "onBubbleDragStart: bubble=" + ((BadgedImageView) bubble).getKey());
-        }
-
-        if (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView())) {
-            return;
-        }
-
-        mExpandedAnimationController.prepareForBubbleDrag(bubble, mMagneticTarget);
-
-        // We're dragging an individual bubble, so set the magnetized object to the magnetized
-        // bubble.
-        mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut();
-        mMagnetizedObject.setMagnetListener(mIndividualBubbleMagnetListener);
-
-        maybeShowManageEducation(false);
+    /**
+     * If you're here because you're not receiving touch events on a view that is a descendant of
+     * BubbleStackView, and you think BSV is intercepting them - it's not! You need to subtract the
+     * bounds of the view in question in {@link #subtractObscuredTouchableRegion}. The ActivityView
+     * consumes all touch events within its bounds, even for views like the BubbleStackView that are
+     * above it. It ignores typical view touch handling methods like this one and
+     * dispatchTouchEvent.
+     */
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        return super.onInterceptTouchEvent(ev);
     }
 
-    /** Called with the coordinates to which an individual bubble has been dragged. */
-    public void onBubbleDragged(View bubble, float x, float y) {
-        if (!mIsExpanded || mIsExpansionAnimating
-                || (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView()))) {
-            return;
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        boolean dispatched = super.dispatchTouchEvent(ev);
+
+        // If a new bubble arrives while the collapsed stack is being dragged, it will be positioned
+        // at the front of the stack (under the touch position). Subsequent ACTION_MOVE events will
+        // then be passed to the new bubble, which will not consume them since it hasn't received an
+        // ACTION_DOWN yet. Work around this by passing MotionEvents directly to the touch handler
+        // until the current gesture ends with an ACTION_UP event.
+        if (!dispatched && !mIsExpanded && mIsGestureInProgress) {
+            dispatched = mBubbleTouchListener.onTouch(this /* view */, ev);
         }
 
-        mExpandedAnimationController.dragBubbleOut(bubble, x, y);
-        springInDismissTarget();
+        mIsGestureInProgress =
+                ev.getAction() != MotionEvent.ACTION_UP
+                        && ev.getAction() != MotionEvent.ACTION_CANCEL;
+
+        return dispatched;
     }
 
-    /** Called when a drag operation on an individual bubble has finished. */
-    public void onBubbleDragFinish(
-            View bubble, float x, float y, float velX, float velY) {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "onBubbleDragFinish: bubble=" + bubble);
-        }
-
-        if (!mIsExpanded || mIsExpansionAnimating
-                || (mBubbleOverflow != null && bubble.equals(mBubbleOverflow.getIconView()))) {
-            return;
-        }
-
-        mExpandedAnimationController.snapBubbleBack(bubble, velX, velY);
-        hideDismissTarget();
-    }
-
-    /** Expands the clicked bubble. */
-    public void expandBubble(Bubble bubble) {
-        if (bubble != null && bubble.equals(mBubbleData.getSelectedBubble())) {
-            // If the bubble we're supposed to expand is the selected bubble, that means the
-            // overflow bubble is currently expanded. Don't tell BubbleData to set this bubble as
-            // selected, since it already is. Just call the stack's setSelectedBubble to expand it.
-            setSelectedBubble(bubble);
-        } else {
-            mBubbleData.setSelectedBubble(bubble);
-        }
-    }
-
-    void onDragStart() {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "onDragStart()");
-        }
-        if (mIsExpanded || mIsExpansionAnimating) {
-            if (DEBUG_BUBBLE_STACK_VIEW) {
-                Log.d(TAG, "mIsExpanded or mIsExpansionAnimating");
-            }
-            return;
-        }
-        mStackAnimationController.cancelStackPositionAnimations();
-        mBubbleContainer.setActiveController(mStackAnimationController);
-        hideFlyoutImmediate();
-
-        // Since we're dragging the stack, set the magnetized object to the magnetized stack.
-        mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
-        mMagnetizedObject.setMagnetListener(mStackMagnetListener);
-    }
-
-    void onDragged(float x, float y) {
-        if (mIsExpanded || mIsExpansionAnimating) {
-            return;
-        }
-
-        hideStackUserEducation(false /* fromExpansion */);
-        springInDismissTarget();
-        mStackAnimationController.moveStackFromTouch(x, y);
-    }
-
-    void onDragFinish(float x, float y, float velX, float velY) {
-        if (DEBUG_BUBBLE_STACK_VIEW) {
-            Log.d(TAG, "onDragFinish");
-        }
-
-        if (mIsExpanded || mIsExpansionAnimating) {
-            return;
-        }
-
-        final float newStackX = mStackAnimationController.flingStackThenSpringToEdge(x, velX, velY);
-        logBubbleEvent(null /* no bubble associated with bubble stack move */,
-                SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
-
-        mStackOnLeftOrWillBe = newStackX <= 0;
-        updateBubbleZOrdersAndDotPosition(true /* animate */);
-        hideDismissTarget();
-    }
-
-    void onFlyoutDragStart() {
-        mFlyout.removeCallbacks(mHideFlyout);
-    }
-
-    void onFlyoutDragged(float deltaX) {
+    void setFlyoutStateForDragLength(float deltaX) {
         // This shouldn't happen, but if it does, just wait until the flyout lays out. This method
         // is continually called.
         if (mFlyout.getWidth() <= 0) {
@@ -1538,61 +1638,29 @@
         mFlyout.setTranslationX(mFlyout.getRestingTranslationX() + overscrollTranslation);
     }
 
-    void onFlyoutTapped() {
-        if (maybeShowStackUserEducation()) {
-            // If we're showing user education, don't open the bubble show the education first
-            mBubbleToExpandAfterFlyoutCollapse = null;
-        } else {
-            mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
-        }
-
-        mFlyout.removeCallbacks(mHideFlyout);
-        mHideFlyout.run();
-    }
-
-    /**
-     * Called when the flyout drag has finished, and returns true if the gesture successfully
-     * dismissed the flyout.
-     */
-    void onFlyoutDragFinished(float deltaX, float velX) {
-        final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
-        final boolean metRequiredVelocity =
-                onLeft ? velX < -FLYOUT_DISMISS_VELOCITY : velX > FLYOUT_DISMISS_VELOCITY;
-        final boolean metRequiredDeltaX =
-                onLeft
-                        ? deltaX < -mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS
-                        : deltaX > mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS;
-        final boolean isCancelFling = onLeft ? velX > 0 : velX < 0;
-        final boolean shouldDismiss = metRequiredVelocity || (metRequiredDeltaX && !isCancelFling);
-
-        mFlyout.removeCallbacks(mHideFlyout);
-        animateFlyoutCollapsed(shouldDismiss, velX);
-
-        maybeShowStackUserEducation();
-    }
-
-    /**
-     * Called when the first touch event of a gesture (stack drag, bubble drag, flyout drag, etc.)
-     * is received.
-     */
-    void onGestureStart() {
-        mIsGestureInProgress = true;
-    }
-
-    /** Called when a gesture is completed or cancelled. */
-    void onGestureFinished() {
-        mIsGestureInProgress = false;
-
-        if (mIsExpanded) {
-            mExpandedAnimationController.onGestureFinished();
-        }
-    }
-
     /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */
-    boolean passEventToMagnetizedObject(MotionEvent event) {
+    private boolean passEventToMagnetizedObject(MotionEvent event) {
         return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
     }
 
+    /**
+     * Dismisses the magnetized object - either an individual bubble, if we're expanded, or the
+     * stack, if we're collapsed.
+     */
+    private void dismissMagnetizedObject() {
+        if (mIsExpanded) {
+            final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject();
+            final Bubble draggedOutBubble = mBubbleData.getBubbleWithView(draggedOutBubbleView);
+
+            if (mBubbleData.hasBubbleWithKey(draggedOutBubble.getKey())) {
+                mBubbleData.notificationEntryRemoved(
+                        draggedOutBubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+            }
+        } else {
+            mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
+        }
+    }
+
     /** Prepares and starts the desaturate/darken animation on the bubble stack. */
     private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
         mDesaturateAndDarkenTargetView = targetView;
@@ -1624,7 +1692,7 @@
     }
 
     /** Animates in the dismiss target. */
-    private void springInDismissTarget() {
+    private void springInDismissTargetMaybe() {
         if (mShowingDismiss) {
             return;
         }
@@ -1827,13 +1895,6 @@
         return 0;
     }
 
-    private boolean isIntersecting(View view, float x, float y) {
-        mTempLoc = view.getLocationOnScreen();
-        mTempRect.set(mTempLoc[0], mTempLoc[1], mTempLoc[0] + view.getWidth(),
-                mTempLoc[1] + view.getHeight());
-        return mTempRect.contains(x, y);
-    }
-
     private void requestUpdate() {
         if (mViewUpdatedRequested || mIsExpansionAnimating) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
deleted file mode 100644
index 132c45f..0000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bubbles;
-
-import android.content.Context;
-import android.graphics.PointF;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.systemui.Dependency;
-
-/**
- * Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
- * dismissing, and flings.
- */
-class BubbleTouchHandler implements View.OnTouchListener {
-
-    private final PointF mTouchDown = new PointF();
-    private final PointF mViewPositionOnTouchDown = new PointF();
-    private final BubbleStackView mStack;
-    private final BubbleData mBubbleData;
-
-    private BubbleController mController = Dependency.get(BubbleController.class);
-
-    private boolean mMovedEnough;
-    private int mTouchSlopSquared;
-    private VelocityTracker mVelocityTracker;
-
-    /** View that was initially touched, when we received the first ACTION_DOWN event. */
-    private View mTouchedView;
-
-    BubbleTouchHandler(BubbleStackView stackView,
-            BubbleData bubbleData, Context context) {
-        final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
-        mTouchSlopSquared = touchSlop * touchSlop;
-        mBubbleData = bubbleData;
-        mStack = stackView;
-    }
-
-    @Override
-    public boolean onTouch(View v, MotionEvent event) {
-        final int action = event.getActionMasked();
-
-        // If we aren't currently in the process of touching a view, figure out what we're touching.
-        // It'll be the stack, an individual bubble, or nothing.
-        if (mTouchedView == null) {
-            mTouchedView = mStack.getTargetView(event);
-        }
-
-        // If this is an ACTION_OUTSIDE event, or the stack reported that we aren't touching
-        // anything, collapse the stack.
-        if (action == MotionEvent.ACTION_OUTSIDE || mTouchedView == null) {
-            mBubbleData.setExpanded(false);
-            mStack.hideStackUserEducation(false /* fromExpansion */);
-            resetForNextGesture();
-            return false;
-        }
-
-        if (!(mTouchedView instanceof BadgedImageView)
-                && !(mTouchedView instanceof BubbleStackView)
-                && !(mTouchedView instanceof BubbleFlyoutView)) {
-
-            // Not touching anything touchable, but we shouldn't collapse (e.g. touching edge
-            // of expanded view).
-            mStack.maybeShowManageEducation(false);
-            resetForNextGesture();
-            return false;
-        }
-
-        final boolean isStack = mStack.equals(mTouchedView);
-        final boolean isFlyout = mStack.getFlyoutView().equals(mTouchedView);
-        final float rawX = event.getRawX();
-        final float rawY = event.getRawY();
-
-        // The coordinates of the touch event, in terms of the touched view's position.
-        final float viewX = mViewPositionOnTouchDown.x + rawX - mTouchDown.x;
-        final float viewY = mViewPositionOnTouchDown.y + rawY - mTouchDown.y;
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-                trackMovement(event);
-
-                mTouchDown.set(rawX, rawY);
-                mStack.onGestureStart();
-
-                if (isStack) {
-                    mViewPositionOnTouchDown.set(mStack.getStackPosition());
-
-                    // Dismiss the entire stack if it's released in the dismiss target.
-                    mStack.setReleasedInDismissTargetAction(
-                            () -> mController.dismissStack(BubbleController.DISMISS_USER_GESTURE));
-                    mStack.onDragStart();
-                    mStack.passEventToMagnetizedObject(event);
-                } else if (isFlyout) {
-                    mStack.onFlyoutDragStart();
-                } else {
-                    mViewPositionOnTouchDown.set(
-                            mTouchedView.getTranslationX(), mTouchedView.getTranslationY());
-
-                    // Dismiss only the dragged-out bubble if it's released in the target.
-                    final String individualBubbleKey = ((BadgedImageView) mTouchedView).getKey();
-                    mStack.setReleasedInDismissTargetAction(() -> {
-                        final Bubble bubble =
-                                mBubbleData.getBubbleWithKey(individualBubbleKey);
-                        // bubble can be null if the user is in the middle of
-                        // dismissing the bubble, but the app also sent a cancel
-                        if (bubble != null) {
-                            mController.removeBubble(bubble.getEntry(),
-                                    BubbleController.DISMISS_USER_GESTURE);
-                        }
-                    });
-
-                    mStack.onBubbleDragStart(mTouchedView);
-                    mStack.passEventToMagnetizedObject(event);
-                }
-
-                break;
-            case MotionEvent.ACTION_MOVE:
-                trackMovement(event);
-                final float deltaX = rawX - mTouchDown.x;
-                final float deltaY = rawY - mTouchDown.y;
-
-                if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) {
-                    mMovedEnough = true;
-                }
-
-                if (mMovedEnough) {
-                    if (isFlyout) {
-                        mStack.onFlyoutDragged(deltaX);
-                    } else if (!mStack.passEventToMagnetizedObject(event)) {
-                        // If the magnetic target doesn't consume the event, drag the stack or
-                        // bubble.
-                        if (isStack) {
-                            mStack.onDragged(viewX, viewY);
-                        } else {
-                            mStack.onBubbleDragged(mTouchedView, viewX, viewY);
-                        }
-                    }
-                }
-                break;
-
-            case MotionEvent.ACTION_CANCEL:
-                resetForNextGesture();
-                break;
-
-            case MotionEvent.ACTION_UP:
-                trackMovement(event);
-                mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
-                final float velX = mVelocityTracker.getXVelocity();
-                final float velY = mVelocityTracker.getYVelocity();
-
-                if (isFlyout && mMovedEnough) {
-                    mStack.onFlyoutDragFinished(rawX - mTouchDown.x /* deltaX */, velX);
-                } else if (isFlyout) {
-                    if (!mBubbleData.isExpanded() && !mMovedEnough) {
-                        mStack.onFlyoutTapped();
-                    }
-                } else if (mMovedEnough) {
-                    if (!mStack.passEventToMagnetizedObject(event)) {
-                        // If the magnetic target didn't consume the event, tell the stack to finish
-                        // the drag.
-                        if (isStack) {
-                            mStack.onDragFinish(viewX, viewY, velX, velY);
-                        } else {
-                            mStack.onBubbleDragFinish(mTouchedView, viewX, viewY, velX, velY);
-                        }
-                    }
-                } else if (mTouchedView == mStack.getExpandedBubbleView()) {
-                    mBubbleData.setExpanded(false);
-                } else if (isStack) {
-                    mStack.onStackTapped();
-                } else {
-                    final String key = ((BadgedImageView) mTouchedView).getKey();
-                    if (key == BubbleOverflow.KEY) {
-                        mStack.showOverflow();
-                    } else {
-                        mStack.expandBubble(mBubbleData.getBubbleWithKey(key));
-                    }
-                }
-                resetForNextGesture();
-                break;
-        }
-
-        return true;
-    }
-
-    /** Clears all touch-related state. */
-    private void resetForNextGesture() {
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
-
-        mTouchedView = null;
-        mMovedEnough = false;
-
-        mStack.onGestureFinished();
-    }
-
-    private void trackMovement(MotionEvent event) {
-        if (mVelocityTracker == null) {
-            mVelocityTracker = VelocityTracker.obtain();
-        }
-        mVelocityTracker.addMovement(event);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index a0b4938..d974adc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -252,8 +252,14 @@
         mSpringToTouchOnNextMotionEvent = true;
     }
 
-    /** Prepares the given bubble to be dragged out. */
-    public void prepareForBubbleDrag(View bubble, MagnetizedObject.MagneticTarget target) {
+    /**
+     * Prepares the given bubble view to be dragged out, using the provided magnetic target and
+     * listener.
+     */
+    public void prepareForBubbleDrag(
+            View bubble,
+            MagnetizedObject.MagneticTarget target,
+            MagnetizedObject.MagnetListener listener) {
         mLayout.cancelAnimationsOnView(bubble);
 
         bubble.setTranslationZ(Short.MAX_VALUE);
@@ -277,6 +283,7 @@
             }
         };
         mMagnetizedBubbleDraggingOut.addTarget(target);
+        mMagnetizedBubbleDraggingOut.setMagnetListener(listener);
         mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
         mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
index c292769..b1bbafc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -1117,9 +1117,4 @@
             mAssociatedController = controller;
         }
     }
-
-    @Override
-    protected boolean canReceivePointerEvents() {
-        return false;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index 5d03fc5..7e8fec7 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -45,7 +45,7 @@
     companion object {
         private const val TAG = "ControlsBindingControllerImpl"
         private const val MAX_CONTROLS_REQUEST = 100000L
-        private const val SUGGESTED_CONTROLS_REQUEST = 4L
+        private const val SUGGESTED_CONTROLS_REQUEST = 6L
     }
 
     private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
@@ -61,6 +61,11 @@
      */
     private var statefulControlSubscriber: StatefulControlSubscriber? = null
 
+    /*
+     * Will track any active load subscriber. Only one can be active at any time.
+     */
+    private var loadSubscriber: LoadSubscriber? = null
+
     private val actionCallbackService = object : IControlsActionCallback.Stub() {
         override fun accept(
             token: IBinder,
@@ -99,17 +104,24 @@
         component: ComponentName,
         callback: ControlsBindingController.LoadCallback
     ): Runnable {
-        val subscriber = LoadSubscriber(callback, MAX_CONTROLS_REQUEST)
-        retrieveLifecycleManager(component).maybeBindAndLoad(subscriber)
-        return subscriber.loadCancel()
+        loadSubscriber?.loadCancel()
+
+        val ls = LoadSubscriber(callback, MAX_CONTROLS_REQUEST)
+        loadSubscriber = ls
+
+        retrieveLifecycleManager(component).maybeBindAndLoad(ls)
+        return ls.loadCancel()
     }
 
     override fun bindAndLoadSuggested(
         component: ComponentName,
         callback: ControlsBindingController.LoadCallback
     ) {
-        val subscriber = LoadSubscriber(callback, SUGGESTED_CONTROLS_REQUEST)
-        retrieveLifecycleManager(component).maybeBindAndLoadSuggested(subscriber)
+        loadSubscriber?.loadCancel()
+        val ls = LoadSubscriber(callback, SUGGESTED_CONTROLS_REQUEST)
+        loadSubscriber = ls
+
+        retrieveLifecycleManager(component).maybeBindAndLoadSuggested(ls)
     }
 
     override fun subscribe(structureInfo: StructureInfo) {
@@ -152,13 +164,16 @@
     override fun changeUser(newUser: UserHandle) {
         if (newUser == currentUser) return
 
-        unsubscribe()
         unbind()
-        currentProvider = null
         currentUser = newUser
     }
 
     private fun unbind() {
+        unsubscribe()
+
+        loadSubscriber?.loadCancel()
+        loadSubscriber = null
+
         currentProvider?.unbindService()
         currentProvider = null
     }
@@ -210,6 +225,20 @@
         val callback: ControlsBindingController.LoadCallback
     ) : CallbackRunnable(token) {
         override fun doRun() {
+            Log.d(TAG, "LoadSubscription: Complete and loading controls")
+            callback.accept(list)
+        }
+    }
+
+    private inner class OnCancelAndLoadRunnable(
+        token: IBinder,
+        val list: List<Control>,
+        val subscription: IControlsSubscription,
+        val callback: ControlsBindingController.LoadCallback
+    ) : CallbackRunnable(token) {
+        override fun doRun() {
+            Log.d(TAG, "LoadSubscription: Canceling and loading controls")
+            provider?.cancelSubscription(subscription)
             callback.accept(list)
         }
     }
@@ -220,6 +249,7 @@
         val requestLimit: Long
     ) : CallbackRunnable(token) {
         override fun doRun() {
+            Log.d(TAG, "LoadSubscription: Starting subscription")
             provider?.startSubscription(subscription, requestLimit)
         }
     }
@@ -254,34 +284,54 @@
         val requestLimit: Long
     ) : IControlsSubscriber.Stub() {
         val loadedControls = ArrayList<Control>()
-        var hasError = false
+        private var isTerminated = false
         private var _loadCancelInternal: (() -> Unit)? = null
+        private lateinit var subscription: IControlsSubscription
+
         fun loadCancel() = Runnable {
-                Log.d(TAG, "Cancel load requested")
-                _loadCancelInternal?.invoke()
-            }
+            Log.d(TAG, "Cancel load requested")
+            _loadCancelInternal?.invoke()
+        }
 
         override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
-            _loadCancelInternal = subs::cancel
+            subscription = subs
+            _loadCancelInternal = { currentProvider?.cancelSubscription(subscription) }
             backgroundExecutor.execute(OnSubscribeRunnable(token, subs, requestLimit))
         }
 
         override fun onNext(token: IBinder, c: Control) {
-            backgroundExecutor.execute { loadedControls.add(c) }
+            backgroundExecutor.execute {
+                if (isTerminated) return@execute
+
+                loadedControls.add(c)
+
+                // Once we have reached our requestLimit, send a request to cancel, and immediately
+                // load the results. Calls to onError() and onComplete() are not required after
+                // cancel.
+                if (loadedControls.size >= requestLimit) {
+                    maybeTerminateAndRun(
+                        OnCancelAndLoadRunnable(token, loadedControls, subscription, callback)
+                    )
+                }
+            }
         }
+
         override fun onError(token: IBinder, s: String) {
-            hasError = true
-            _loadCancelInternal = {}
-            currentProvider?.cancelLoadTimeout()
-            backgroundExecutor.execute(OnLoadErrorRunnable(token, s, callback))
+            maybeTerminateAndRun(OnLoadErrorRunnable(token, s, callback))
         }
 
         override fun onComplete(token: IBinder) {
+            maybeTerminateAndRun(OnLoadRunnable(token, loadedControls, callback))
+        }
+
+        private fun maybeTerminateAndRun(postTerminateFn: Runnable) {
+            if (isTerminated) return
+
+            isTerminated = true
             _loadCancelInternal = {}
-            if (!hasError) {
-                currentProvider?.cancelLoadTimeout()
-                backgroundExecutor.execute(OnLoadRunnable(token, loadedControls, callback))
-            }
+            currentProvider?.cancelLoadTimeout()
+
+            backgroundExecutor.execute(postTerminateFn)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index ae75dd4..568fb28 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -180,6 +180,11 @@
     fun countFavoritesForComponent(componentName: ComponentName): Int
 
     /**
+     * TEMPORARY for testing
+     */
+    fun resetFavorites()
+
+    /**
      * Interface for structure to pass data to [ControlsFavoritingActivity].
      */
     interface LoadData {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 3483339..8805694 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -365,6 +365,8 @@
         componentName: ComponentName,
         callback: Consumer<Boolean>
     ) {
+        if (seedingInProgress) return
+
         Log.i(TAG, "Beginning request to seed favorites for: $componentName")
         if (!confirmAvailability()) {
             if (userChanging) {
@@ -495,6 +497,13 @@
         }
     }
 
+    override fun resetFavorites() {
+        executor.execute {
+            Favorites.clear()
+            persistenceWrapper.storeFavorites(Favorites.getAllStructures())
+        }
+    }
+
     override fun refreshStatus(componentName: ComponentName, control: Control) {
         if (!confirmAvailability()) {
             Log.d(TAG, "Controls not available")
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
index 895f1d2..a6af6a1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -63,8 +63,6 @@
 ) : IBinder.DeathRecipient {
 
     val token: IBinder = Binder()
-    @GuardedBy("subscriptions")
-    private val subscriptions = mutableListOf<IControlsSubscription>()
     private var requiresBound = false
     @GuardedBy("queuedServiceMethods")
     private val queuedServiceMethods: MutableSet<ServiceMethod> = ArraySet()
@@ -194,7 +192,7 @@
      * Request a call to [IControlsProvider.loadSuggested].
      *
      * If the service is not bound, the call will be queued and the service will be bound first.
-     * The service will be unbound after the controls are returned or the call times out.
+     * The service will be unbound if the call times out.
      *
      * @param subscriber the subscriber that manages coordination for loading controls
      */
@@ -245,9 +243,7 @@
         if (DEBUG) {
             Log.d(TAG, "startSubscription: $subscription")
         }
-        synchronized(subscriptions) {
-            subscriptions.add(subscription)
-        }
+
         wrapper?.request(subscription, requestLimit)
     }
 
@@ -261,9 +257,7 @@
         if (DEBUG) {
             Log.d(TAG, "cancelSubscription: $subscription")
         }
-        synchronized(subscriptions) {
-            subscriptions.remove(subscription)
-        }
+
         wrapper?.cancel(subscription)
     }
 
@@ -281,17 +275,6 @@
         onLoadCanceller?.run()
         onLoadCanceller = null
 
-        // be sure to cancel all subscriptions
-        val subs = synchronized(subscriptions) {
-            ArrayList(subscriptions).also {
-                subscriptions.clear()
-            }
-        }
-
-        subs.forEach {
-            wrapper?.cancel(it)
-        }
-
         bindService(false)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 208d911..7da3d70 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -16,18 +16,26 @@
 
 package com.android.systemui.controls.ui
 
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ObjectAnimator
+import android.app.AlertDialog
 import android.app.Dialog
 import android.content.ComponentName
 import android.content.Context
+import android.content.DialogInterface
 import android.content.Intent
 import android.content.SharedPreferences
 import android.content.res.Configuration
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.LayerDrawable
+import android.os.Process
 import android.service.controls.Control
 import android.service.controls.actions.ControlAction
 import android.util.TypedValue
 import android.util.Log
+import android.view.animation.AccelerateInterpolator
+import android.view.animation.DecelerateInterpolator
 import android.view.ContextThemeWrapper
 import android.view.LayoutInflater
 import android.view.View
@@ -77,6 +85,8 @@
         private const val PREF_COMPONENT = "controls_component"
         private const val PREF_STRUCTURE = "controls_structure"
 
+        private const val FADE_IN_MILLIS = 225L
+
         private val EMPTY_COMPONENT = ComponentName("", "")
         private val EMPTY_STRUCTURE = StructureInfo(
             EMPTY_COMPONENT,
@@ -153,7 +163,20 @@
 
     private fun reload(parent: ViewGroup) {
         if (hidden) return
-        show(parent)
+
+        val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f)
+        fadeAnim.setInterpolator(AccelerateInterpolator(1.0f))
+        fadeAnim.setDuration(FADE_IN_MILLIS)
+        fadeAnim.addListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator) {
+                show(parent)
+                val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f)
+                showAnim.setInterpolator(DecelerateInterpolator(1.0f))
+                showAnim.setDuration(FADE_IN_MILLIS)
+                showAnim.start()
+            }
+        })
+        fadeAnim.start()
     }
 
     private fun showSeedingView(items: List<SelectionItem>) {
@@ -229,7 +252,8 @@
 
     private fun createMenu() {
         val items = arrayOf(
-            context.resources.getString(R.string.controls_menu_add)
+            context.resources.getString(R.string.controls_menu_add),
+            "Reset"
         )
         var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items)
 
@@ -249,6 +273,8 @@
                             when (pos) {
                                 // 0: Add Control
                                 0 -> startFavoritingActivity(view.context, selectedStructure)
+                                // 1: TEMPORARY for reset controls
+                                1 -> showResetConfirmation()
                                 else -> Log.w(ControlsUiController.TAG,
                                     "Unsupported index ($pos) on 'more' menu selection")
                             }
@@ -275,6 +301,39 @@
         })
     }
 
+    private fun showResetConfirmation() {
+        val builder = AlertDialog.Builder(
+            context,
+            android.R.style.Theme_DeviceDefault_Dialog_Alert
+        ).apply {
+            setMessage("For testing purposes: Would you like to " +
+                "reset your favorited device controls?")
+            setPositiveButton(
+                android.R.string.ok,
+                DialogInterface.OnClickListener { dialog, _ ->
+                    val userHandle = Process.myUserHandle()
+                    val userContext = context.createContextAsUser(userHandle, 0)
+                    val prefs = userContext.getSharedPreferences(
+                        "controls_prefs", Context.MODE_PRIVATE)
+                    prefs.edit().putBoolean("ControlsSeedingCompleted", false).apply()
+                    controlsController.get().resetFavorites()
+                    dialog.dismiss()
+                    context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
+            })
+            setNegativeButton(
+                android.R.string.cancel,
+                DialogInterface.OnClickListener {
+                    dialog, _ -> dialog.cancel()
+                }
+            )
+        }
+        builder.create().apply {
+            getWindow().apply {
+                setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
+            }
+        }.show()
+    }
+
     private fun createDropDown(items: List<SelectionItem>) {
         items.forEach {
             RenderInfo.registerComponentIcon(it.componentName, it.icon)
@@ -302,7 +361,6 @@
                 .setTint(context.resources.getColor(R.color.control_spinner_dropdown, null))
         }
         parent.requireViewById<ImageView>(R.id.app_icon).apply {
-            setContentDescription(selectionItem.getTitle())
             setImageDrawable(selectionItem.icon)
         }
 
@@ -370,7 +428,8 @@
         }
 
         // add spacers if necessary to keep control size consistent
-        var spacersToAdd = selectedStructure.controls.size % maxColumns
+        val mod = selectedStructure.controls.size % maxColumns
+        var spacersToAdd = if (mod == 0) 0 else maxColumns - mod
         while (spacersToAdd > 0) {
             lastRow.addView(Space(context), LinearLayout.LayoutParams(0, 0, 1f))
             spacersToAdd--
@@ -526,7 +585,6 @@
             setText(item.getTitle())
         }
         view.requireViewById<ImageView>(R.id.app_icon).apply {
-            setContentDescription(item.appName)
             setImageDrawable(item.icon)
         }
         return view
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
index d33cd94..124df32 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
@@ -56,20 +56,19 @@
             enabled: Boolean,
             offset: Int = 0
         ): RenderInfo {
-            val (fg, bg) = deviceColorMap.getValue(deviceType)
-
-            val iconKey = if (offset > 0) {
+            val key = if (offset > 0) {
                 deviceType * BUCKET_SIZE + offset
             } else deviceType
 
-            val iconState = deviceIconMap.getValue(iconKey)
+            val (fg, bg) = deviceColorMap.getValue(key)
+            val iconState = deviceIconMap.getValue(key)
             val resourceId = iconState[enabled]
             var icon: Drawable?
             if (resourceId == APP_ICON_ID) {
                 icon = appIconMap.get(componentName)
                 if (icon == null) {
                     icon = context.resources
-                        .getDrawable(R.drawable.ic_device_unknown_gm2_24px, null)
+                        .getDrawable(R.drawable.ic_device_unknown_on, null)
                     appIconMap.put(componentName, icon)
                 }
             } else {
@@ -268,10 +267,74 @@
     DeviceTypes.TYPE_ROUTINE to IconState(
         RenderInfo.APP_ICON_ID,
         RenderInfo.APP_ICON_ID
+    ),
+    DeviceTypes.TYPE_AC_HEATER to IconState(
+        R.drawable.ic_device_thermostat_off,
+        R.drawable.ic_device_thermostat_on
+    ),
+    DeviceTypes.TYPE_AC_UNIT to IconState(
+        R.drawable.ic_device_thermostat_off,
+        R.drawable.ic_device_thermostat_on
+    ),
+    DeviceTypes.TYPE_COFFEE_MAKER to IconState(
+        R.drawable.ic_device_kettle_off,
+        R.drawable.ic_device_kettle_on
+    ),
+    DeviceTypes.TYPE_DEHUMIDIFIER to IconState(
+        R.drawable.ic_device_air_freshener_off,
+        R.drawable.ic_device_air_freshener_on
+    ),
+    DeviceTypes.TYPE_RADIATOR to IconState(
+        R.drawable.ic_device_thermostat_off,
+        R.drawable.ic_device_thermostat_on
+    ),
+    DeviceTypes.TYPE_STANDMIXER to IconState(
+        R.drawable.ic_device_cooking_off,
+        R.drawable.ic_device_cooking_on
+    ),
+    DeviceTypes.TYPE_DISPLAY to IconState(
+        R.drawable.ic_device_display_off,
+        R.drawable.ic_device_display_on
+    ),
+    DeviceTypes.TYPE_DRYER to IconState(
+        R.drawable.ic_device_washer_off,
+        R.drawable.ic_device_washer_on
+    ),
+    DeviceTypes.TYPE_MOWER to IconState(
+        R.drawable.ic_device_outdoor_garden_off,
+        R.drawable.ic_device_outdoor_garden_on
+    ),
+    DeviceTypes.TYPE_SHOWER to IconState(
+        R.drawable.ic_device_water_off,
+        R.drawable.ic_device_water_on
+    ),
+    DeviceTypes.TYPE_AWNING to IconState(
+        R.drawable.ic_device_pergola_off,
+        R.drawable.ic_device_pergola_on
+    ),
+    DeviceTypes.TYPE_CLOSET to IconState(
+        R.drawable.ic_device_drawer_off,
+        R.drawable.ic_device_drawer_on
+    ),
+    DeviceTypes.TYPE_CURTAIN to IconState(
+        R.drawable.ic_device_blinds_off,
+        R.drawable.ic_device_blinds_on
+    ),
+    DeviceTypes.TYPE_DOOR to IconState(
+        R.drawable.ic_device_door_off,
+        R.drawable.ic_device_door_on
+    ),
+    DeviceTypes.TYPE_SHUTTER to IconState(
+        R.drawable.ic_device_window_off,
+        R.drawable.ic_device_window_on
+    ),
+    DeviceTypes.TYPE_HEATER to IconState(
+        R.drawable.ic_device_thermostat_off,
+        R.drawable.ic_device_thermostat_on
     )
 ).withDefault {
     IconState(
-        R.drawable.ic_device_unknown_gm2_24px,
-        R.drawable.ic_device_unknown_gm2_24px
+        R.drawable.ic_device_unknown_off,
+        R.drawable.ic_device_unknown_on
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index 5b3d5c5..82ccb17 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -74,6 +74,8 @@
 import com.android.systemui.statusbar.policy.ZenModeControllerImpl;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerServiceImpl;
+import com.android.systemui.util.RingerModeTracker;
+import com.android.systemui.util.RingerModeTrackerImpl;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
 import com.android.systemui.volume.VolumeDialogControllerImpl;
@@ -264,4 +266,10 @@
     @Binds
     public abstract VolumeComponent provideVolumeComponent(
             VolumeDialogComponent volumeDialogComponent);
+
+    /**
+     */
+    @Binds
+    public abstract RingerModeTracker provideRingerModeTracker(
+            RingerModeTrackerImpl ringerModeTrackerImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
index c16dce1..3f88f25 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
@@ -40,7 +40,7 @@
 
     private int mDockState = DockManager.STATE_NONE;
 
-    public DozeDockHandler(AmbientDisplayConfiguration config, DozeMachine machine,
+    DozeDockHandler(AmbientDisplayConfiguration config, DozeMachine machine,
             DockManager dockManager) {
         mMachine = machine;
         mConfig = config;
@@ -74,8 +74,13 @@
         @Override
         public void onEvent(int dockState) {
             if (DEBUG) Log.d(TAG, "dock event = " + dockState);
-            final DozeMachine.State nextState;
+
             mDockState = dockState;
+            if (isPulsing()) {
+                return;
+            }
+
+            DozeMachine.State nextState;
             switch (mDockState) {
                 case DockManager.STATE_DOCKED:
                     nextState = State.DOZE_AOD_DOCKED;
@@ -90,10 +95,15 @@
                 default:
                     return;
             }
-
             mMachine.requestState(nextState);
         }
 
+        private boolean isPulsing() {
+            DozeMachine.State state = mMachine.getState();
+            return state == State.DOZE_REQUEST_PULSE || state == State.DOZE_PULSING
+                    || state == State.DOZE_PULSING_BRIGHT;
+        }
+
         void register() {
             if (mRegistered) {
                 return;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index f7f9afd..18bfd89 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -339,8 +339,8 @@
             return State.DOZE;
         }
         if ((mState == State.DOZE_AOD_PAUSED || mState == State.DOZE_AOD_PAUSING
-                || mState == State.DOZE_AOD || mState == State.DOZE)
-                && requestedState == State.DOZE_PULSE_DONE) {
+                || mState == State.DOZE_AOD || mState == State.DOZE
+                || mState == State.DOZE_AOD_DOCKED) && requestedState == State.DOZE_PULSE_DONE) {
             Log.i(TAG, "Dropping pulse done because current state is already done: " + mState);
             return mState;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 4dd5e87..a4a5894 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -39,9 +39,11 @@
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.content.pm.UserInfo;
+import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
@@ -70,6 +72,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
@@ -77,6 +80,11 @@
 import android.widget.ImageView.ScaleType;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
@@ -113,6 +121,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.EmergencyDialerConstants;
+import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.leak.RotationUtils;
 import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator;
 
@@ -129,7 +138,8 @@
 public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
         DialogInterface.OnShowListener,
         ConfigurationController.ConfigurationListener,
-        GlobalActionsPanelPlugin.Callbacks {
+        GlobalActionsPanelPlugin.Callbacks,
+        LifecycleOwner {
 
     public static final String SYSTEM_DIALOG_REASON_KEY = "reason";
     public static final String SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS = "globalactions";
@@ -178,6 +188,9 @@
     private final NotificationShadeDepthController mDepthController;
     private final BlurUtils mBlurUtils;
 
+    // Used for RingerModeTracker
+    private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
+
     private ArrayList<Action> mItems;
     private ActionsDialog mDialog;
 
@@ -188,7 +201,7 @@
 
     private boolean mKeyguardShowing = false;
     private boolean mDeviceProvisioned = false;
-    private ToggleAction.State mAirplaneState = ToggleAction.State.Off;
+    private ToggleState mAirplaneState = ToggleState.Off;
     private boolean mIsWaitingForEcmExit = false;
     private boolean mHasTelephony;
     private boolean mHasVibrator;
@@ -205,7 +218,10 @@
     private final IWindowManager mIWindowManager;
     private final Executor mBackgroundExecutor;
     private final ControlsListingController mControlsListingController;
-    private boolean mAnyControlsProviders = false;
+    private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>();
+    private ControlsController mControlsController;
+    private SharedPreferences mControlsPreferences;
+    private final RingerModeTracker mRingerModeTracker;
 
     @VisibleForTesting
     public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
@@ -244,7 +260,8 @@
             ControlsUiController controlsUiController, IWindowManager iWindowManager,
             @Background Executor backgroundExecutor,
             ControlsListingController controlsListingController,
-            ControlsController controlsController, UiEventLogger uiEventLogger) {
+            ControlsController controlsController, UiEventLogger uiEventLogger,
+            RingerModeTracker ringerModeTracker) {
         mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
@@ -271,6 +288,8 @@
         mBackgroundExecutor = backgroundExecutor;
         mControlsListingController = controlsListingController;
         mBlurUtils = blurUtils;
+        mRingerModeTracker = ringerModeTracker;
+        mControlsController = controlsController;
 
         // receive broadcasts
         IntentFilter filter = new IntentFilter();
@@ -290,6 +309,11 @@
 
         mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean(
                 R.bool.config_useFixedVolume);
+        if (mShowSilentToggle) {
+            mRingerModeTracker.getRingerMode().observe(this, ringer ->
+                    mHandler.sendEmptyMessage(MESSAGE_REFRESH)
+            );
+        }
 
         mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
         mScreenshotHelper = new ScreenshotHelper(context);
@@ -309,45 +333,54 @@
             }
         });
 
-        String preferredControlsPackage = mContext.getResources()
-                .getString(com.android.systemui.R.string.config_controlsPreferredPackage);
         mControlsListingController.addCallback(list -> {
-            mAnyControlsProviders = !list.isEmpty();
-
-            /*
-             * See if any service providers match the preferred component. If they do,
-             * and there are no current favorites, and we haven't successfully loaded favorites to
-             * date, query the preferred component for a limited number of suggested controls.
-             */
-            ComponentName preferredComponent = null;
-            for (ControlsServiceInfo info : list) {
-                if (info.componentName.getPackageName().equals(preferredControlsPackage)) {
-                    preferredComponent = info.componentName;
-                    break;
-                }
-            }
-
-            if (preferredComponent == null) return;
-
-            SharedPreferences prefs = context.getSharedPreferences(PREFS_CONTROLS_FILE,
-                    Context.MODE_PRIVATE);
-            boolean isSeeded = prefs.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false);
-            boolean hasFavorites = controlsController.getFavorites().size() > 0;
-            if (!isSeeded && !hasFavorites) {
-                controlsController.seedFavoritesForComponent(
-                        preferredComponent,
-                        (accepted) -> {
-                            Log.i(TAG, "Controls seeded: " + accepted);
-                            prefs.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED,
-                                    accepted).apply();
-                        }
-                );
-            }
+            mControlsServiceInfos = list;
         });
+
+        // Need to be user-specific with the context to make sure we read the correct prefs
+        Context userContext = context.createContextAsUser(
+                new UserHandle(mUserManager.getUserHandle()), 0);
+        mControlsPreferences = userContext.getSharedPreferences(PREFS_CONTROLS_FILE,
+            Context.MODE_PRIVATE);
+
     }
 
+    private void seedFavorites() {
+        if (mControlsServiceInfos.isEmpty()
+                || mControlsController.getFavorites().size() > 0
+                || mControlsPreferences.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false)) {
+            return;
+        }
 
+        /*
+         * See if any service providers match the preferred component. If they do,
+         * and there are no current favorites, and we haven't successfully loaded favorites to
+         * date, query the preferred component for a limited number of suggested controls.
+         */
+        String preferredControlsPackage = mContext.getResources()
+                .getString(com.android.systemui.R.string.config_controlsPreferredPackage);
 
+        ComponentName preferredComponent = null;
+        for (ControlsServiceInfo info : mControlsServiceInfos) {
+            if (info.componentName.getPackageName().equals(preferredControlsPackage)) {
+                preferredComponent = info.componentName;
+                break;
+            }
+        }
+
+        if (preferredComponent == null) {
+            Log.i(TAG, "Controls seeding: No preferred component has been set, will not seed");
+            mControlsPreferences.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, true).apply();
+        }
+
+        mControlsController.seedFavoritesForComponent(
+                preferredComponent,
+                (accepted) -> {
+                    Log.i(TAG, "Controls seeded: " + accepted);
+                    mControlsPreferences.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED,
+                        accepted).apply();
+                });
+    }
 
     /**
      * Show the global actions dialog (creating if necessary)
@@ -393,6 +426,7 @@
         awakenIfNecessary();
         mDialog = createDialog();
         prepareDialog();
+        seedFavorites();
 
         // If we only have 1 item and it's a simple press action, just do this action.
         if (mAdapter.getCount() == 1
@@ -594,7 +628,7 @@
 
         @Override
         public boolean shouldBeSeparated() {
-            return true;
+            return !shouldShowControls();
         }
 
         @Override
@@ -602,7 +636,12 @@
                 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
             View v = super.create(context, convertView, parent, inflater);
             int textColor;
-            if (shouldBeSeparated()) {
+            if (shouldShowControls()) {
+                v.setBackgroundTintList(ColorStateList.valueOf(v.getResources().getColor(
+                        com.android.systemui.R.color.global_actions_emergency_background)));
+                textColor = v.getResources().getColor(
+                        com.android.systemui.R.color.global_actions_emergency_text);
+            } else if (shouldBeSeparated()) {
                 textColor = v.getResources().getColor(
                         com.android.systemui.R.color.global_actions_alert_text);
             } else {
@@ -612,7 +651,7 @@
             TextView messageView = v.findViewById(R.id.message);
             messageView.setTextColor(textColor);
             messageView.setSelected(true); // necessary for marquee to work
-            ImageView icon = (ImageView) v.findViewById(R.id.icon);
+            ImageView icon = v.findViewById(R.id.icon);
             icon.getDrawable().setTint(textColor);
             return v;
         }
@@ -982,18 +1021,15 @@
         refreshSilentMode();
         mAirplaneModeOn.updateState(mAirplaneState);
         mAdapter.notifyDataSetChanged();
-        if (mShowSilentToggle) {
-            IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
-            mBroadcastDispatcher.registerReceiver(mRingerModeReceiver, filter);
-        }
+        mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
     }
 
     private void refreshSilentMode() {
         if (!mHasVibrator) {
-            final boolean silentModeOn =
-                    mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
+            Integer value = mRingerModeTracker.getRingerMode().getValue();
+            final boolean silentModeOn = value != null && value != AudioManager.RINGER_MODE_NORMAL;
             ((ToggleAction) mSilentModeAction).updateState(
-                    silentModeOn ? ToggleAction.State.On : ToggleAction.State.Off);
+                    silentModeOn ? ToggleState.On : ToggleState.Off);
         }
     }
 
@@ -1005,14 +1041,7 @@
             mDialog = null;
         }
         mWindowManagerFuncs.onGlobalActionsHidden();
-        if (mShowSilentToggle) {
-            try {
-                mBroadcastDispatcher.unregisterReceiver(mRingerModeReceiver);
-            } catch (IllegalArgumentException ie) {
-                // ignore this
-                Log.w(TAG, ie);
-            }
-        }
+        mLifecycle.setCurrentState(Lifecycle.State.DESTROYED);
     }
 
     /**
@@ -1023,6 +1052,13 @@
         mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_OPEN);
     }
 
+    private int getActionLayoutId() {
+        if (shouldShowControls()) {
+            return com.android.systemui.R.layout.global_actions_grid_item_v2;
+        }
+        return com.android.systemui.R.layout.global_actions_grid_item;
+    }
+
     /**
      * The adapter used for the list within the global actions dialog, taking into account whether
      * the keyguard is showing via
@@ -1234,20 +1270,12 @@
             }
         }
 
-        protected int getActionLayoutId(Context context) {
-            if (shouldShowControls()) {
-                return com.android.systemui.R.layout.global_actions_grid_item_v2;
-            }
-            return com.android.systemui.R.layout.global_actions_grid_item;
-        }
-
         public View create(
                 Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
-            View v = inflater.inflate(getActionLayoutId(context), parent,
-                    false);
+            View v = inflater.inflate(getActionLayoutId(), parent, false /* attach */);
 
-            ImageView icon = (ImageView) v.findViewById(R.id.icon);
-            TextView messageView = (TextView) v.findViewById(R.id.message);
+            ImageView icon = v.findViewById(R.id.icon);
+            TextView messageView = v.findViewById(R.id.message);
             messageView.setSelected(true); // necessary for marquee to work
 
             if (mIcon != null) {
@@ -1266,30 +1294,30 @@
         }
     }
 
+    private enum ToggleState {
+        Off(false),
+        TurningOn(true),
+        TurningOff(true),
+        On(false);
+
+        private final boolean mInTransition;
+
+        ToggleState(boolean intermediate) {
+            mInTransition = intermediate;
+        }
+
+        public boolean inTransition() {
+            return mInTransition;
+        }
+    }
+
     /**
      * A toggle action knows whether it is on or off, and displays an icon and status message
      * accordingly.
      */
-    private static abstract class ToggleAction implements Action {
+    private abstract class ToggleAction implements Action {
 
-        enum State {
-            Off(false),
-            TurningOn(true),
-            TurningOff(true),
-            On(false);
-
-            private final boolean inTransition;
-
-            State(boolean intermediate) {
-                inTransition = intermediate;
-            }
-
-            public boolean inTransition() {
-                return inTransition;
-            }
-        }
-
-        protected State mState = State.Off;
+        protected ToggleState mState = ToggleState.Off;
 
         // prefs
         protected int mEnabledIconResId;
@@ -1333,13 +1361,12 @@
                 LayoutInflater inflater) {
             willCreate();
 
-            View v = inflater.inflate(com.android.systemui.R
-                    .layout.global_actions_grid_item, parent, false);
+            View v = inflater.inflate(getActionLayoutId(), parent, false /* attach */);
 
             ImageView icon = (ImageView) v.findViewById(R.id.icon);
             TextView messageView = (TextView) v.findViewById(R.id.message);
             final boolean enabled = isEnabled();
-            boolean on = ((mState == State.On) || (mState == State.TurningOn));
+            boolean on = ((mState == ToggleState.On) || (mState == ToggleState.TurningOn));
 
             if (messageView != null) {
                 messageView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
@@ -1364,7 +1391,7 @@
                 return;
             }
 
-            final boolean nowOn = !(mState == State.On);
+            final boolean nowOn = !(mState == ToggleState.On);
             onToggle(nowOn);
             changeStateFromPress(nowOn);
         }
@@ -1381,12 +1408,12 @@
          * @param buttonOn Whether the button was turned on or off
          */
         protected void changeStateFromPress(boolean buttonOn) {
-            mState = buttonOn ? State.On : State.Off;
+            mState = buttonOn ? ToggleState.On : ToggleState.Off;
         }
 
         abstract void onToggle(boolean on);
 
-        public void updateState(State state) {
+        public void updateState(ToggleState state) {
             mState = state;
         }
     }
@@ -1420,7 +1447,7 @@
 
             // In ECM mode airplane state cannot be changed
             if (!TelephonyProperties.in_ecm_mode().orElse(false)) {
-                mState = buttonOn ? State.TurningOn : State.TurningOff;
+                mState = buttonOn ? ToggleState.TurningOn : ToggleState.TurningOff;
                 mAirplaneState = mState;
             }
         }
@@ -1555,21 +1582,12 @@
         public void onServiceStateChanged(ServiceState serviceState) {
             if (!mHasTelephony) return;
             final boolean inAirplaneMode = serviceState.getState() == ServiceState.STATE_POWER_OFF;
-            mAirplaneState = inAirplaneMode ? ToggleAction.State.On : ToggleAction.State.Off;
+            mAirplaneState = inAirplaneMode ? ToggleState.On : ToggleState.Off;
             mAirplaneModeOn.updateState(mAirplaneState);
             mAdapter.notifyDataSetChanged();
         }
     };
 
-    private BroadcastReceiver mRingerModeReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
-                mHandler.sendEmptyMessage(MESSAGE_REFRESH);
-            }
-        }
-    };
-
     private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) {
         @Override
         public void onChange(boolean selfChange) {
@@ -1614,7 +1632,7 @@
                 mContentResolver,
                 Settings.Global.AIRPLANE_MODE_ON,
                 0) == 1;
-        mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
+        mAirplaneState = airplaneModeOn ? ToggleState.On : ToggleState.Off;
         mAirplaneModeOn.updateState(mAirplaneState);
     }
 
@@ -1631,10 +1649,16 @@
         intent.putExtra("state", on);
         mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
         if (!mHasTelephony) {
-            mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off;
+            mAirplaneState = on ? ToggleState.On : ToggleState.Off;
         }
     }
 
+    @NonNull
+    @Override
+    public Lifecycle getLifecycle() {
+        return mLifecycle;
+    }
+
     private static final class ActionsDialog extends Dialog implements DialogInterface,
             ColorExtractor.OnColorsChangedListener {
 
@@ -1888,6 +1912,14 @@
                                 mGlobalActionsLayout);
                     })
                     .start();
+            ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView();
+            root.setOnApplyWindowInsetsListener((v, windowInsets) -> {
+                if (mControlsUiController != null) {
+                    Insets insets = windowInsets.getInsets(WindowInsets.Type.all());
+                    root.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+                }
+                return WindowInsets.CONSUMED;
+            });
             if (mControlsUiController != null) {
                 mControlsUiController.show(mControlsView);
             }
@@ -2017,6 +2049,6 @@
     private boolean shouldShowControls() {
         return mKeyguardStateController.isUnlocked()
                 && mControlsUiController.getAvailable()
-                && mAnyControlsProviders;
+                && !mControlsServiceInfos.isEmpty();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java b/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java
new file mode 100644
index 0000000..71bc7c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControllerFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media;
+
+import android.content.Context;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+
+import javax.inject.Inject;
+
+/**
+ * Testable wrapper around {@link MediaController} constructor.
+ */
+public class MediaControllerFactory {
+
+    private final Context mContext;
+
+    @Inject
+    public MediaControllerFactory(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Creates a new MediaController from a session's token.
+     *
+     * @param token The token for the session. This value must never be null.
+     */
+    public MediaController create(MediaSession.Token token) {
+        return new MediaController(mContext, token);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
index aa5ebaa..b7658a9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
@@ -46,19 +46,6 @@
     /** Updates seek bar views when the data model changes. */
     @UiThread
     override fun onChanged(data: SeekBarViewModel.Progress) {
-        if (data.enabled && seekBarView.visibility == View.GONE) {
-            seekBarView.visibility = View.VISIBLE
-            elapsedTimeView.visibility = View.VISIBLE
-            totalTimeView.visibility = View.VISIBLE
-        } else if (!data.enabled && seekBarView.visibility == View.VISIBLE) {
-            seekBarView.visibility = View.GONE
-            elapsedTimeView.visibility = View.GONE
-            totalTimeView.visibility = View.GONE
-            return
-        }
-
-        // TODO: update the style of the disabled progress bar
-        seekBarView.setEnabled(data.seekAvailable)
 
         data.color?.let {
             var tintList = ColorStateList.valueOf(it)
@@ -71,6 +58,17 @@
             totalTimeView.setTextColor(it)
         }
 
+        if (!data.enabled) {
+            seekBarView.setEnabled(false)
+            seekBarView.getThumb().setAlpha(0)
+            elapsedTimeView.setText("")
+            totalTimeView.setText("")
+            return
+        }
+
+        seekBarView.getThumb().setAlpha(if (data.seekAvailable) 255 else 0)
+        seekBarView.setEnabled(data.seekAvailable)
+
         data.elapsedTime?.let {
             seekBarView.setProgress(it)
             elapsedTimeView.setText(DateUtils.formatElapsedTime(
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index d219a9e..dba4343 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -319,6 +319,7 @@
                     getSurfaceTransactionHelper()
                             .crop(tx, leash, getDestinationBounds())
                             .round(tx, leash, shouldApplyCornerRadius());
+                    tx.show(leash);
                     tx.apply();
                 }
             };
@@ -359,6 +360,7 @@
                     getSurfaceTransactionHelper()
                             .alpha(tx, leash, 1f)
                             .round(tx, leash, shouldApplyCornerRadius());
+                    tx.show(leash);
                     tx.apply();
                 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 1868536..e24d29f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -231,7 +231,12 @@
     /**
      * @return {@link Rect} of the destination PiP window bounds.
      */
-    Rect getDestinationBounds(float aspectRatio, Rect bounds, Size minimalSize) {
+    Rect getDestinationBounds(ComponentName componentName, float aspectRatio, Rect bounds,
+            Size minimalSize) {
+        if (!componentName.equals(mLastPipComponentName)) {
+            onResetReentryBoundsUnchecked();
+            mLastPipComponentName = componentName;
+        }
         final Rect destinationBounds;
         if (bounds == null) {
             final Rect defaultBounds = getDefaultBounds(mReentrySnapFraction, mReentrySize);
@@ -246,11 +251,7 @@
             transformBoundsToAspectRatio(destinationBounds, aspectRatio,
                     false /* useCurrentMinEdgeSize */);
         }
-        if (destinationBounds.equals(bounds)) {
-            return bounds;
-        }
         mAspectRatio = aspectRatio;
-        onResetReentryBoundsUnchecked();
         mLastDestinationBounds.set(destinationBounds);
         return destinationBounds;
     }
@@ -483,6 +484,7 @@
         pw.println(prefix + TAG);
         pw.println(innerPrefix + "mLastPipComponentName=" + mLastPipComponentName);
         pw.println(innerPrefix + "mReentrySnapFraction=" + mReentrySnapFraction);
+        pw.println(innerPrefix + "mReentrySize=" + mReentrySize);
         pw.println(innerPrefix + "mDisplayInfo=" + mDisplayInfo);
         pw.println(innerPrefix + "mDefaultAspectRatio=" + mDefaultAspectRatio);
         pw.println(innerPrefix + "mMinAspectRatio=" + mMinAspectRatio);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index d9872d7..d2994ae 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -103,6 +103,7 @@
         @Override
         public void onPipAnimationEnd(SurfaceControl.Transaction tx,
                 PipAnimationController.PipTransitionAnimator animator) {
+            finishResize(tx, animator.getDestinationBounds(), animator.getTransitionDirection());
             mMainHandler.post(() -> {
                 for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
                     final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
@@ -110,7 +111,6 @@
                             animator.getTransitionDirection());
                 }
             });
-            finishResize(tx, animator.getDestinationBounds(), animator.getTransitionDirection());
         }
 
         @Override
@@ -247,7 +247,7 @@
     public void onTaskAppeared(ActivityManager.RunningTaskInfo info) {
         Objects.requireNonNull(info, "Requires RunningTaskInfo");
         final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                getAspectRatioOrDefault(info.pictureInPictureParams),
+                info.topActivity, getAspectRatioOrDefault(info.pictureInPictureParams),
                 null /* bounds */, getMinimalSize(info.topActivityInfo));
         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
         mTaskInfo = info;
@@ -303,7 +303,7 @@
             return;
         }
         final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                getAspectRatioOrDefault(newParams),
+                info.topActivity, getAspectRatioOrDefault(newParams),
                 null /* bounds */, getMinimalSize(info.topActivityInfo));
         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
         scheduleAnimateResizePip(destinationBounds, mEnterExitAnimationDuration,
@@ -335,7 +335,7 @@
         }
 
         final Rect newDestinationBounds = mPipBoundsHandler.getDestinationBounds(
-                getAspectRatioOrDefault(mTaskInfo.pictureInPictureParams),
+                mTaskInfo.topActivity, getAspectRatioOrDefault(mTaskInfo.pictureInPictureParams),
                 null /* bounds */, getMinimalSize(mTaskInfo.topActivityInfo));
         if (newDestinationBounds.equals(currentDestinationBounds)) return;
         if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
deleted file mode 100644
index b725811..0000000
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.pip.phone;
-
-import android.content.Context;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.VibrationEffect;
-import android.os.Vibrator;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.widget.FrameLayout;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.shared.system.WindowManagerWrapper;
-
-/**
- * Displays the dismiss UI and target for floating objects.
- */
-public class PipDismissViewController {
-
-    // This delay controls how long to wait before we show the target when the user first moves
-    // the PIP, to prevent the target from animating if the user just wants to fling the PIP
-    public static final int SHOW_TARGET_DELAY = 100;
-    private static final int SHOW_TARGET_DURATION = 350;
-    private static final int HIDE_TARGET_DURATION = 225;
-
-    private Context mContext;
-    private WindowManager mWindowManager;
-    private View mDismissView;
-
-    // Used for dismissing a bubble -- bubble should be in the target to be considered a dismiss
-    private View mTargetView;
-    private int mTargetSlop;
-    private Point mWindowSize;
-    private int[] mLoc = new int[2];
-    private boolean mIntersecting;
-    private Vibrator mVibe;
-
-    public PipDismissViewController(Context context) {
-        mContext = context;
-        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        mVibe = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
-    }
-
-    /**
-     * Creates the dismiss target for showing via {@link #showDismissTarget()}.
-     */
-    public void createDismissTarget() {
-        if (mDismissView == null) {
-            // Determine sizes for the view
-            final Rect stableInsets = new Rect();
-            WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
-            mWindowSize = new Point();
-            mWindowManager.getDefaultDisplay().getRealSize(mWindowSize);
-            final int gradientHeight = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.pip_dismiss_gradient_height);
-            final int bottomMargin = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.pip_dismiss_text_bottom_margin);
-            mTargetSlop = mContext.getResources().getDimensionPixelSize(
-                    R.dimen.bubble_dismiss_slop);
-
-            // Create a new view for the dismiss target
-            LayoutInflater inflater = LayoutInflater.from(mContext);
-            mDismissView = inflater.inflate(R.layout.pip_dismiss_view, null);
-            mDismissView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
-            mDismissView.forceHasOverlappingRendering(false);
-
-            // Set the gradient background
-            Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim);
-            gradient.setAlpha((int) (255 * 0.85f));
-            mDismissView.setBackground(gradient);
-
-            // Adjust bottom margins of the text
-            mTargetView = mDismissView.findViewById(R.id.pip_dismiss_text);
-            FrameLayout.LayoutParams tlp = (FrameLayout.LayoutParams) mTargetView.getLayoutParams();
-            tlp.bottomMargin = stableInsets.bottom + bottomMargin;
-            mTargetView.setLayoutParams(tlp);
-
-            // Add the target to the window
-            LayoutParams lp =  new LayoutParams(
-                    LayoutParams.MATCH_PARENT, gradientHeight,
-                    0, mWindowSize.y - gradientHeight,
-                    LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
-                    LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                            | LayoutParams.FLAG_NOT_TOUCHABLE
-                            | LayoutParams.FLAG_NOT_FOCUSABLE,
-                    PixelFormat.TRANSLUCENT);
-            lp.setTitle("pip-dismiss-overlay");
-            lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-            lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
-            lp.setFitInsetsTypes(0 /* types */);
-            mWindowManager.addView(mDismissView, lp);
-        }
-        mDismissView.animate().cancel();
-    }
-
-
-    /**
-     * Updates the dismiss target based on location of the view, only used for bubbles not for PIP.
-     *
-     * @return whether the view is within the dismiss target.
-     */
-    public boolean updateTarget(View view) {
-        if (mDismissView == null) {
-            return false;
-        }
-        if (mDismissView.getAlpha() > 0) {
-            view.getLocationOnScreen(mLoc);
-            Rect viewRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + view.getWidth(),
-                    mLoc[1] + view.getHeight());
-            mTargetView.getLocationOnScreen(mLoc);
-            Rect targetRect = new Rect(mLoc[0], mLoc[1], mLoc[0] + mTargetView.getWidth(),
-                    mLoc[1] + mTargetView.getHeight());
-            expandRect(targetRect, mTargetSlop);
-            boolean intersecting = targetRect.intersect(viewRect);
-            if (intersecting != mIntersecting) {
-                // TODO: is this the right effect?
-                mVibe.vibrate(VibrationEffect.get(intersecting
-                        ? VibrationEffect.EFFECT_CLICK
-                        : VibrationEffect.EFFECT_TICK));
-            }
-            mIntersecting = intersecting;
-            return intersecting;
-        }
-        return false;
-    }
-
-    /**
-     * Shows the dismiss target.
-     */
-    public void showDismissTarget() {
-        mDismissView.animate()
-                .alpha(1f)
-                .setInterpolator(Interpolators.LINEAR)
-                .setStartDelay(SHOW_TARGET_DELAY)
-                .setDuration(SHOW_TARGET_DURATION)
-                .start();
-    }
-
-    /**
-     * Hides and destroys the dismiss target.
-     */
-    public void destroyDismissTarget() {
-        if (mDismissView != null) {
-            mDismissView.animate()
-                    .alpha(0f)
-                    .setInterpolator(Interpolators.LINEAR)
-                    .setStartDelay(0)
-                    .setDuration(HIDE_TARGET_DURATION)
-                    .withEndAction(new Runnable() {
-                        @Override
-                        public void run() {
-                            mWindowManager.removeViewImmediate(mDismissView);
-                            mDismissView = null;
-                        }
-                    })
-                    .start();
-        }
-    }
-
-    private void expandRect(Rect outRect, int expandAmount) {
-        outRect.left = Math.max(0, outRect.left - expandAmount);
-        outRect.top = Math.max(0, outRect.top - expandAmount);
-        outRect.right = Math.min(mWindowSize.x, outRect.right + expandAmount);
-        outRect.bottom = Math.min(mWindowSize.y, outRect.bottom + expandAmount);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index 8397c65..a8a5d89 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -16,16 +16,12 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager.StackInfo;
 import android.app.IActivityTaskManager;
 import android.content.Context;
-import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
 import android.os.Debug;
 import android.os.RemoteException;
@@ -40,6 +36,7 @@
 import com.android.systemui.util.FloatingContentCoordinator;
 import com.android.systemui.util.animation.FloatProperties;
 import com.android.systemui.util.animation.PhysicsAnimator;
+import com.android.systemui.util.magnetictarget.MagnetizedObject;
 
 import java.io.PrintWriter;
 import java.util.function.Consumer;
@@ -61,9 +58,6 @@
     /** Friction to use for PIP when it moves via physics fling animations. */
     private static final float DEFAULT_FRICTION = 2f;
 
-    // The fraction of the stack height that the user has to drag offscreen to dismiss the PiP
-    private static final float DISMISS_OFFSCREEN_FRACTION = 0.3f;
-
     private final Context mContext;
     private final IActivityTaskManager mActivityTaskManager;
     private final PipTaskOrganizer mPipTaskOrganizer;
@@ -103,7 +97,7 @@
     /**
      * Update listener that resizes the PIP to {@link #mAnimatedBounds}.
      */
-    private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener =
+    final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener =
             (target, values) -> resizePipUnchecked(mAnimatedBounds);
 
     /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */
@@ -122,6 +116,13 @@
 
     private final Consumer<Rect> mUpdateBoundsCallback = mBounds::set;
 
+    /**
+     * Whether we're springing to the touch event location (vs. moving it to that position
+     * instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was
+     * 'stuck' in the target and needs to catch up to the touch location.
+     */
+    private boolean mSpringingToTouch = false;
+
     public PipMotionHelper(Context context, IActivityTaskManager activityTaskManager,
             PipTaskOrganizer pipTaskOrganizer, PipMenuActivityController menuController,
             PipSnapAlgorithm snapAlgorithm, FlingAnimationUtils flingAnimationUtils,
@@ -166,15 +167,7 @@
      */
     void synchronizePinnedStackBounds() {
         cancelAnimations();
-        try {
-            StackInfo stackInfo = mActivityTaskManager.getStackInfo(
-                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
-            if (stackInfo != null) {
-                mBounds.set(stackInfo.bounds);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to get pinned stack bounds");
-        }
+        mBounds.set(mPipTaskOrganizer.getLastReportedBounds());
     }
 
     /**
@@ -211,9 +204,35 @@
             mFloatingContentCoordinator.onContentMoved(this);
         }
 
-        cancelAnimations();
-        resizePipUnchecked(toBounds);
-        mBounds.set(toBounds);
+        if (!mSpringingToTouch) {
+            // If we are moving PIP directly to the touch event locations, cancel any animations and
+            // move PIP to the given bounds.
+            cancelAnimations();
+            resizePipUnchecked(toBounds);
+            mBounds.set(toBounds);
+        } else {
+            // If PIP is 'catching up' after being stuck in the dismiss target, update the animation
+            // to spring towards the new touch location.
+            mAnimatedBoundsPhysicsAnimator
+                    .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig)
+                    .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig)
+                    .withEndActions(() -> mSpringingToTouch = false);
+
+            startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */);
+        }
+    }
+
+    /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */
+    void setSpringingToTouch(boolean springingToTouch) {
+        if (springingToTouch) {
+            mAnimatedBounds.set(mBounds);
+        }
+
+        mSpringingToTouch = springingToTouch;
+    }
+
+    void prepareForAnimation() {
+        mAnimatedBounds.set(mBounds);
     }
 
     /**
@@ -278,24 +297,11 @@
     }
 
     /**
-     * @return whether the PiP at the current bounds should be dismissed.
-     */
-    boolean shouldDismissPip() {
-        Point displaySize = new Point();
-        mContext.getDisplay().getRealSize(displaySize);
-        final int y = displaySize.y - mStableInsets.bottom;
-        if (mBounds.bottom > y) {
-            float offscreenFraction = (float) (mBounds.bottom - y) / mBounds.height();
-            return offscreenFraction >= DISMISS_OFFSCREEN_FRACTION;
-        }
-        return false;
-    }
-
-    /**
      * Flings the PiP to the closest snap target.
      */
     void flingToSnapTarget(
-            float velocityX, float velocityY, Runnable updateAction, @Nullable Runnable endAction) {
+            float velocityX, float velocityY,
+            @Nullable Runnable updateAction, @Nullable Runnable endAction) {
         mAnimatedBounds.set(mBounds);
         mAnimatedBoundsPhysicsAnimator
                 .flingThenSpring(
@@ -303,9 +309,13 @@
                         true /* flingMustReachMinOrMax */)
                 .flingThenSpring(
                         FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig)
-                .addUpdateListener((target, values) -> updateAction.run())
                 .withEndActions(endAction);
 
+        if (updateAction != null) {
+            mAnimatedBoundsPhysicsAnimator.addUpdateListener(
+                    (target, values) -> updateAction.run());
+        }
+
         final float xEndValue = velocityX < 0 ? mMovementBounds.left : mMovementBounds.right;
         final float estimatedFlingYEndValue =
                 PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY);
@@ -338,16 +348,14 @@
      * Animates the dismissal of the PiP off the edge of the screen.
      */
     void animateDismiss(float velocityX, float velocityY, @Nullable Runnable updateAction) {
-        final float velocity = PointF.length(velocityX, velocityY);
-        final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
-        final Point dismissEndPoint = getDismissEndPoint(mBounds, velocityX, velocityY, isFling);
-
         mAnimatedBounds.set(mBounds);
 
-        // Animate to the dismiss end point, and then dismiss PIP.
+        // Animate off the bottom of the screen, then dismiss PIP.
         mAnimatedBoundsPhysicsAnimator
-                .spring(FloatProperties.RECT_X, dismissEndPoint.x, velocityX, mSpringConfig)
-                .spring(FloatProperties.RECT_Y, dismissEndPoint.y, velocityY, mSpringConfig)
+                .spring(FloatProperties.RECT_Y,
+                        mBounds.bottom + mBounds.height(),
+                        velocityY,
+                        mSpringConfig)
                 .withEndActions(this::dismissPip);
 
         // If we were provided with an update action, run it whenever there's an update.
@@ -356,7 +364,7 @@
                     (target, values) -> updateAction.run());
         }
 
-        startBoundsAnimator(dismissEndPoint.x /* toX */, dismissEndPoint.y /* toY */);
+        startBoundsAnimator(mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */);
     }
 
     /**
@@ -408,6 +416,7 @@
     private void cancelAnimations() {
         mAnimatedBoundsPhysicsAnimator.cancel();
         mAnimatingToBounds.setEmpty();
+        mSpringingToTouch = false;
     }
 
     /** Set new fling configs whose min/max values respect the given movement bounds. */
@@ -426,7 +435,9 @@
      * the 'real' bounds to equal the final animated bounds.
      */
     private void startBoundsAnimator(float toX, float toY) {
-        cancelAnimations();
+        if (!mSpringingToTouch) {
+            cancelAnimations();
+        }
 
         // Set animatingToBounds directly to avoid allocating a new Rect, but then call
         // setAnimatingToBounds to run the normal logic for changing animatingToBounds.
@@ -484,47 +495,29 @@
     }
 
     /**
-     * @return the coordinates the PIP should animate to based on the direction of velocity when
-     *         dismissing.
+     * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the
+     * magnetic dismiss target so it can calculate PIP's size and position.
      */
-    private Point getDismissEndPoint(Rect pipBounds, float velX, float velY, boolean isFling) {
-        Point displaySize = new Point();
-        mContext.getDisplay().getRealSize(displaySize);
-        final float bottomBound = displaySize.y + pipBounds.height() * .1f;
-        if (isFling && velX != 0 && velY != 0) {
-            // Line is defined by: y = mx + b, m = slope, b = y-intercept
-            // Find the slope
-            final float slope = velY / velX;
-            // Sub in slope and PiP position to solve for y-intercept: b = y - mx
-            final float yIntercept = pipBounds.top - slope * pipBounds.left;
-            // Now find the point on this line when y = bottom bound: x = (y - b) / m
-            final float x = (bottomBound - yIntercept) / slope;
-            return new Point((int) x, (int) bottomBound);
-        } else {
-            // If it wasn't a fling the velocity on 'up' is not reliable for direction of movement,
-            // just animate downwards.
-            return new Point(pipBounds.left, (int) bottomBound);
-        }
-    }
+    MagnetizedObject<Rect> getMagnetizedPip() {
+        return new MagnetizedObject<Rect>(
+                mContext, mAnimatedBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) {
+            @Override
+            public float getWidth(@NonNull Rect animatedPipBounds) {
+                return animatedPipBounds.width();
+            }
 
-    /**
-     * @return whether the gesture it towards the dismiss area based on the velocity when
-     *         dismissing.
-     */
-    public boolean isGestureToDismissArea(Rect pipBounds, float velX, float velY,
-            boolean isFling) {
-        Point endpoint = getDismissEndPoint(pipBounds, velX, velY, isFling);
-        // Center the point
-        endpoint.x += pipBounds.width() / 2;
-        endpoint.y += pipBounds.height() / 2;
+            @Override
+            public float getHeight(@NonNull Rect animatedPipBounds) {
+                return animatedPipBounds.height();
+            }
 
-        // The dismiss area is the middle third of the screen, half the PIP's height from the bottom
-        Point size = new Point();
-        mContext.getDisplay().getRealSize(size);
-        final int left = size.x / 3;
-        Rect dismissArea = new Rect(left, size.y - (pipBounds.height() / 2), left * 2,
-                size.y + pipBounds.height());
-        return dismissArea.contains(endpoint.x, endpoint.y);
+            @Override
+            public void getLocationOnScreen(
+                    @NonNull Rect animatedPipBounds, @NonNull int[] loc) {
+                loc[0] = animatedPipBounds.left;
+                loc[1] = animatedPipBounds.top;
+            }
+        };
     }
 
     public void dump(PrintWriter pw, String prefix) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 7cc2759..1e9daab 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -20,11 +20,13 @@
 import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
 import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
 
+import android.annotation.SuppressLint;
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -33,14 +35,23 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.Size;
+import android.view.Gravity;
 import android.view.IPinnedStackController;
 import android.view.InputEvent;
 import android.view.MotionEvent;
+import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.logging.MetricsLoggerWrapper;
@@ -51,7 +62,10 @@
 import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.util.DismissCircleView;
 import com.android.systemui.util.FloatingContentCoordinator;
+import com.android.systemui.util.animation.PhysicsAnimator;
+import com.android.systemui.util.magnetictarget.MagnetizedObject;
 
 import java.io.PrintWriter;
 
@@ -62,9 +76,6 @@
 public class PipTouchHandler {
     private static final String TAG = "PipTouchHandler";
 
-    // Allow the PIP to be flung from anywhere on the screen to the bottom to be dismissed.
-    private static final boolean ENABLE_FLING_DISMISS = false;
-
     private static final int SHOW_DISMISS_AFFORDANCE_DELAY = 225;
     private static final int BOTTOM_OFFSET_BUFFER_DP = 1;
 
@@ -73,17 +84,45 @@
     // Allow PIP to resize to a slightly bigger state upon touch
     private final boolean mEnableResize;
     private final Context mContext;
+    private final WindowManager mWindowManager;
     private final IActivityManager mActivityManager;
     private final PipBoundsHandler mPipBoundsHandler;
     private PipResizeGestureHandler mPipResizeGestureHandler;
     private IPinnedStackController mPinnedStackController;
 
     private final PipMenuActivityController mMenuController;
-    private final PipDismissViewController mDismissViewController;
     private final PipSnapAlgorithm mSnapAlgorithm;
     private final AccessibilityManager mAccessibilityManager;
     private boolean mShowPipMenuOnAnimationEnd = false;
 
+    /**
+     * MagnetizedObject wrapper for PIP. This allows the magnetic target library to locate and move
+     * PIP.
+     */
+    private MagnetizedObject<Rect> mMagnetizedPip;
+
+    /**
+     * Container for the dismiss circle, so that it can be animated within the container via
+     * translation rather than within the WindowManager via slow layout animations.
+     */
+    private ViewGroup mTargetViewContainer;
+
+    /** Circle view used to render the dismiss target. */
+    private DismissCircleView mTargetView;
+
+    /**
+     * MagneticTarget instance wrapping the target view and allowing us to set its magnetic radius.
+     */
+    private MagnetizedObject.MagneticTarget mMagneticTarget;
+
+    /** PhysicsAnimator instance for animating the dismiss target in/out. */
+    private PhysicsAnimator<View> mMagneticTargetAnimator;
+
+    /** Default configuration to use for springing the dismiss target in/out. */
+    private final PhysicsAnimator.SpringConfig mTargetSpringConfig =
+            new PhysicsAnimator.SpringConfig(
+                    SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+
     // The current movement bounds
     private Rect mMovementBounds = new Rect();
     // The current resized bounds, changed by user resize.
@@ -104,21 +143,20 @@
     private int mDeferResizeToNormalBoundsUntilRotation = -1;
     private int mDisplayRotation;
 
+    /**
+     * Runnable that can be posted delayed to show the target. This needs to be saved as a member
+     * variable so we can pass it to removeCallbacks.
+     */
+    private Runnable mShowTargetAction = this::showDismissTargetMaybe;
+
     private Handler mHandler = new Handler();
-    private Runnable mShowDismissAffordance = new Runnable() {
-        @Override
-        public void run() {
-            if (mEnableDismissDragToEdge) {
-                mDismissViewController.showDismissTarget();
-            }
-        }
-    };
 
     // Behaviour states
     private int mMenuState = MENU_STATE_NONE;
     private boolean mIsImeShowing;
     private int mImeHeight;
     private int mImeOffset;
+    private int mDismissAreaHeight;
     private boolean mIsShelfShowing;
     private int mShelfHeight;
     private int mMovementBoundsExtraOffsets;
@@ -168,6 +206,7 @@
         }
     }
 
+    @SuppressLint("InflateParams")
     public PipTouchHandler(Context context, IActivityManager activityManager,
             IActivityTaskManager activityTaskManager, PipMenuActivityController menuController,
             InputConsumerController inputConsumerController,
@@ -180,9 +219,9 @@
         mContext = context;
         mActivityManager = activityManager;
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
+        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
         mMenuController = menuController;
         mMenuController.addListener(new PipMenuListener());
-        mDismissViewController = new PipDismissViewController(context);
         mSnapAlgorithm = pipSnapAlgorithm;
         mFlingAnimationUtils = new FlingAnimationUtils(context.getResources().getDisplayMetrics(),
                 2.5f);
@@ -200,6 +239,7 @@
         mExpandedShortestEdgeSize = res.getDimensionPixelSize(
                 R.dimen.pip_expanded_shortest_edge_size);
         mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
+        mDismissAreaHeight = res.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height);
 
         mEnableDismissDragToEdge = res.getBoolean(R.bool.config_pipEnableDismissDragToEdge);
         mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu);
@@ -212,6 +252,56 @@
         mFloatingContentCoordinator = floatingContentCoordinator;
         mConnection = new PipAccessibilityInteractionConnection(mMotionHelper,
                 this::onAccessibilityShowMenu, mHandler);
+
+        final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
+        mTargetView = new DismissCircleView(context);
+        final FrameLayout.LayoutParams newParams =
+                new FrameLayout.LayoutParams(targetSize, targetSize);
+        newParams.gravity = Gravity.CENTER;
+        mTargetView.setLayoutParams(newParams);
+
+        mTargetViewContainer = new FrameLayout(context);
+        mTargetViewContainer.setClipChildren(false);
+        mTargetViewContainer.addView(mTargetView);
+
+        mMagnetizedPip = mMotionHelper.getMagnetizedPip();
+        mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
+        mMagnetizedPip.setPhysicsAnimatorUpdateListener(mMotionHelper.mResizePipUpdateListener);
+        mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
+            @Override
+            public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+                mMotionHelper.prepareForAnimation();
+
+                // Show the dismiss target, in case the initial touch event occurred within the
+                // magnetic field radius.
+                showDismissTargetMaybe();
+            }
+
+            @Override
+            public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
+                    float velX, float velY, boolean wasFlungOut) {
+                if (wasFlungOut) {
+                    mMotionHelper.flingToSnapTarget(velX, velY, null, null);
+                    hideDismissTarget();
+                } else {
+                    mMotionHelper.setSpringingToTouch(true);
+                }
+            }
+
+            @Override
+            public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
+                mHandler.post(() -> {
+                    mMotionHelper.animateDismiss(0, 0, null);
+                    hideDismissTarget();
+                });
+
+
+                MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext,
+                        PipUtils.getTopPipActivity(mContext, mActivityManager));
+            }
+        });
+
+        mMagneticTargetAnimator = PhysicsAnimator.getInstance(mTargetView);
     }
 
     public void setTouchGesture(PipTouchGesture gesture) {
@@ -231,7 +321,8 @@
     }
 
     public void onActivityPinned() {
-        cleanUpDismissTarget();
+        createDismissTargetMaybe();
+
         mShowPipMenuOnAnimationEnd = true;
         mPipResizeGestureHandler.onActivityPinned();
         mFloatingContentCoordinator.onContentAdded(mMotionHelper);
@@ -264,6 +355,10 @@
     public void onConfigurationChanged() {
         mMotionHelper.onConfigurationChanged();
         mMotionHelper.synchronizePinnedStackBounds();
+
+        // Recreate the dismiss target for the new orientation.
+        cleanUpDismissTarget();
+        createDismissTargetMaybe();
     }
 
     public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
@@ -351,6 +446,74 @@
         }
     }
 
+    /** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
+    private void createDismissTargetMaybe() {
+        if (!mTargetViewContainer.isAttachedToWindow()) {
+            mHandler.removeCallbacks(mShowTargetAction);
+            mMagneticTargetAnimator.cancel();
+
+            final Point windowSize = new Point();
+            mWindowManager.getDefaultDisplay().getRealSize(windowSize);
+            WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                    WindowManager.LayoutParams.MATCH_PARENT,
+                    mDismissAreaHeight,
+                    0, windowSize.y - mDismissAreaHeight,
+                    WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                            | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                    PixelFormat.TRANSLUCENT);
+            lp.setTitle("pip-dismiss-overlay");
+            lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+            lp.setFitInsetsTypes(0 /* types */);
+
+            mTargetViewContainer.setVisibility(View.INVISIBLE);
+            mWindowManager.addView(mTargetViewContainer, lp);
+        }
+    }
+
+    /** Makes the dismiss target visible and animates it in, if it isn't already visible. */
+    private void showDismissTargetMaybe() {
+        createDismissTargetMaybe();
+
+        if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
+
+            mTargetView.setTranslationY(mTargetViewContainer.getHeight());
+            mTargetViewContainer.setVisibility(View.VISIBLE);
+
+            // Set the magnetic field radius to half of PIP's width.
+            mMagneticTarget.setMagneticFieldRadiusPx(mMotionHelper.getBounds().width());
+
+            // Cancel in case we were in the middle of animating it out.
+            mMagneticTargetAnimator.cancel();
+            mMagneticTargetAnimator
+                    .spring(DynamicAnimation.TRANSLATION_Y, 0f, mTargetSpringConfig)
+                    .start();
+        }
+    }
+
+    /** Animates the magnetic dismiss target out and then sets it to GONE. */
+    private void hideDismissTarget() {
+        mHandler.removeCallbacks(mShowTargetAction);
+        mMagneticTargetAnimator
+                .spring(DynamicAnimation.TRANSLATION_Y,
+                        mTargetViewContainer.getHeight(),
+                        mTargetSpringConfig)
+                .withEndActions(() ->  mTargetViewContainer.setVisibility(View.GONE))
+                .start();
+    }
+
+    /**
+     * Removes the dismiss target and cancels any pending callbacks to show it.
+     */
+    private void cleanUpDismissTarget() {
+        mHandler.removeCallbacks(mShowTargetAction);
+
+        if (mTargetViewContainer.isAttachedToWindow()) {
+            mWindowManager.removeViewImmediate(mTargetViewContainer);
+        }
+    }
+
     private void onRegistrationChanged(boolean isRegistered) {
         mAccessibilityManager.setPictureInPictureActionReplacingConnection(isRegistered
                 ? mConnection : null);
@@ -375,8 +538,24 @@
         if (mPinnedStackController == null) {
             return true;
         }
+
         MotionEvent ev = (MotionEvent) inputEvent;
 
+        if (mMagnetizedPip.maybeConsumeMotionEvent(ev)) {
+            // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event
+            // to the touch state. Touch state needs a DOWN event in order to later process MOVE
+            // events it'll receive if the object is dragged out of the magnetic field.
+            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                mTouchState.onTouchEvent(ev);
+            }
+
+            // Continue tracking velocity when the object is in the magnetic field, since we want to
+            // respect touch input velocity if the object is dragged out and then flung.
+            mTouchState.addMovementToVelocityTracker(ev);
+
+            return true;
+        }
+
         // Update the touch state
         mTouchState.onTouchEvent(ev);
 
@@ -413,8 +592,14 @@
                 break;
             }
             case MotionEvent.ACTION_HOVER_ENTER:
-                mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
-                        mMovementBounds, false /* allowMenuTimeout */, false /* willResizeMenu */);
+                // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
+                // on and changing MotionEvents into HoverEvents.
+                // Let's not enable menu show/hide for a11y services.
+                if (!mAccessibilityManager.isTouchExplorationEnabled()) {
+                    mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
+                            mMovementBounds, false /* allowMenuTimeout */,
+                            false /* willResizeMenu */);
+                }
             case MotionEvent.ACTION_HOVER_MOVE: {
                 if (!shouldDeliverToMenu && !mSendingHoverAccessibilityEvents) {
                     sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
@@ -423,7 +608,12 @@
                 break;
             }
             case MotionEvent.ACTION_HOVER_EXIT: {
-                mMenuController.hideMenu();
+                // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably
+                // on and changing MotionEvents into HoverEvents.
+                // Let's not enable menu show/hide for a11y services.
+                if (!mAccessibilityManager.isTouchExplorationEnabled()) {
+                    mMenuController.hideMenu();
+                }
                 if (!shouldDeliverToMenu && mSendingHoverAccessibilityEvents) {
                     sendAccessibilityHoverEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
                     mSendingHoverAccessibilityEvents = false;
@@ -600,17 +790,13 @@
             mDelta.set(0f, 0f);
             mStartPosition.set(bounds.left, bounds.top);
             mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom;
+            mMotionHelper.setSpringingToTouch(false);
 
             // If the menu is still visible then just poke the menu
             // so that it will timeout after the user stops touching it
             if (mMenuState != MENU_STATE_NONE) {
                 mMenuController.pokeMenu();
             }
-
-            if (mEnableDismissDragToEdge) {
-                mDismissViewController.createDismissTarget();
-                mHandler.postDelayed(mShowDismissAffordance, SHOW_DISMISS_AFFORDANCE_DELAY);
-            }
         }
 
         @Override
@@ -623,8 +809,10 @@
                 mSavedSnapFraction = -1f;
 
                 if (mEnableDismissDragToEdge) {
-                    mHandler.removeCallbacks(mShowDismissAffordance);
-                    mDismissViewController.showDismissTarget();
+                    if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
+                        mHandler.removeCallbacks(mShowTargetAction);
+                        showDismissTargetMaybe();
+                    }
                 }
             }
 
@@ -644,10 +832,6 @@
                 mTmpBounds.offsetTo((int) left, (int) top);
                 mMotionHelper.movePip(mTmpBounds, true /* isDragging */);
 
-                if (mEnableDismissDragToEdge) {
-                    updateDismissFraction();
-                }
-
                 final PointF curPos = touchState.getLastTouchPosition();
                 if (mMovementWithinDismiss) {
                     // Track if movement remains near the bottom edge to identify swipe to dismiss
@@ -661,9 +845,7 @@
         @Override
         public boolean onUp(PipTouchState touchState) {
             if (mEnableDismissDragToEdge) {
-                // Clean up the dismiss target regardless of the touch state in case the touch
-                // enabled state changes while the user is interacting
-                cleanUpDismissTarget();
+                hideDismissTarget();
             }
 
             if (!touchState.isUserInteracting()) {
@@ -671,26 +853,8 @@
             }
 
             final PointF vel = touchState.getVelocity();
-            final boolean isHorizontal = Math.abs(vel.x) > Math.abs(vel.y);
             final float velocity = PointF.length(vel.x, vel.y);
             final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond();
-            final boolean isUpWithinDimiss = ENABLE_FLING_DISMISS
-                    && touchState.getLastTouchPosition().y >= mMovementBounds.bottom
-                    && mMotionHelper.isGestureToDismissArea(mMotionHelper.getBounds(), vel.x,
-                            vel.y, isFling);
-            final boolean isFlingToBot = isFling && vel.y > 0 && !isHorizontal
-                    && (mMovementWithinDismiss || isUpWithinDimiss);
-            if (mEnableDismissDragToEdge) {
-                // Check if the user dragged or flung the PiP offscreen to dismiss it
-                if (mMotionHelper.shouldDismissPip() || isFlingToBot) {
-                    MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext,
-                            PipUtils.getTopPipActivity(mContext, mActivityManager));
-                    mMotionHelper.animateDismiss(
-                            vel.x, vel.y,
-                            PipTouchHandler.this::updateDismissFraction /* updateAction */);
-                    return true;
-                }
-            }
 
             if (touchState.isDragging()) {
                 Runnable endAction = null;
@@ -749,14 +913,6 @@
     }
 
     /**
-     * Removes the dismiss target and cancels any pending callbacks to show it.
-     */
-    private void cleanUpDismissTarget() {
-        mHandler.removeCallbacks(mShowDismissAffordance);
-        mDismissViewController.destroyDismissTarget();
-    }
-
-    /**
      * @return whether the menu will resize as a part of showing the full menu.
      */
     private boolean willResizeMenu() {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index e3f65ef..dc286c1 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -92,7 +92,7 @@
 
                 // Initialize the velocity tracker
                 initOrResetVelocityTracker();
-                addMovement(ev);
+                addMovementToVelocityTracker(ev);
 
                 mActivePointerId = ev.getPointerId(0);
                 if (DEBUG) {
@@ -120,7 +120,7 @@
                 }
 
                 // Update the velocity tracker
-                addMovement(ev);
+                addMovementToVelocityTracker(ev);
                 int pointerIndex = ev.findPointerIndex(mActivePointerId);
                 if (pointerIndex == -1) {
                     Log.e(TAG, "Invalid active pointer id on MOVE: " + mActivePointerId);
@@ -151,7 +151,7 @@
                 }
 
                 // Update the velocity tracker
-                addMovement(ev);
+                addMovementToVelocityTracker(ev);
 
                 int pointerIndex = ev.getActionIndex();
                 int pointerId = ev.getPointerId(pointerIndex);
@@ -174,7 +174,7 @@
                 }
 
                 // Update the velocity tracker
-                addMovement(ev);
+                addMovementToVelocityTracker(ev);
                 mVelocityTracker.computeCurrentVelocity(1000,
                         mViewConfig.getScaledMaximumFlingVelocity());
                 mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
@@ -318,6 +318,20 @@
         return -1;
     }
 
+    void addMovementToVelocityTracker(MotionEvent event) {
+        if (mVelocityTracker == null) {
+            return;
+        }
+
+        // Add movement to velocity tracker using raw screen X and Y coordinates instead
+        // of window coordinates because the window frame may be moving at the same time.
+        float deltaX = event.getRawX() - event.getX();
+        float deltaY = event.getRawY() - event.getY();
+        event.offsetLocation(deltaX, deltaY);
+        mVelocityTracker.addMovement(event);
+        event.offsetLocation(-deltaX, -deltaY);
+    }
+
     private void initOrResetVelocityTracker() {
         if (mVelocityTracker == null) {
             mVelocityTracker = VelocityTracker.obtain();
@@ -333,16 +347,6 @@
         }
     }
 
-    private void addMovement(MotionEvent event) {
-        // Add movement to velocity tracker using raw screen X and Y coordinates instead
-        // of window coordinates because the window frame may be moving at the same time.
-        float deltaX = event.getRawX() - event.getX();
-        float deltaY = event.getRawY() - event.getY();
-        event.offsetLocation(deltaX, deltaY);
-        mVelocityTracker.addMovement(event);
-        event.offsetLocation(-deltaX, -deltaY);
-    }
-
     public void dump(PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
index 9c175bc..8efeef1 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java
@@ -30,6 +30,14 @@
  */
 public class PipControlsView extends LinearLayout {
 
+    public PipControlsView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
     public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 52c8960..e25bfaa 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -239,6 +239,12 @@
         mInitialized = true;
         mContext = context;
         mPipBoundsHandler = pipBoundsHandler;
+        // Ensure that we have the display info in case we get calls to update the bounds before the
+        // listener calls back
+        final DisplayInfo displayInfo = new DisplayInfo();
+        context.getDisplay().getDisplayInfo(displayInfo);
+        mPipBoundsHandler.onDisplayInfoChanged(displayInfo);
+
         mResizeAnimationDuration = context.getResources()
                 .getInteger(R.integer.config_pipResizeAnimationDuration);
         mPipTaskOrganizer = new PipTaskOrganizer(mContext, mPipBoundsHandler,
@@ -289,7 +295,6 @@
 
         mMediaSessionManager =
                 (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
-        mPipTaskOrganizer.registerPipTransitionCallback(this);
 
         try {
             WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener);
@@ -334,6 +339,8 @@
      * Shows the picture-in-picture menu if an activity is in picture-in-picture mode.
      */
     public void showPictureInPictureMenu() {
+        if (DEBUG) Log.d(TAG, "showPictureInPictureMenu(), current state=" + getStateDescription());
+
         if (getState() == STATE_PIP) {
             resizePinnedStack(STATE_PIP_MENU);
         }
@@ -343,10 +350,18 @@
      * Closes PIP (PIPed activity and PIP system UI).
      */
     public void closePip() {
+        if (DEBUG) Log.d(TAG, "closePip(), current state=" + getStateDescription());
+
         closePipInternal(true);
     }
 
     private void closePipInternal(boolean removePipStack) {
+        if (DEBUG) {
+            Log.d(TAG,
+                    "closePipInternal() removePipStack=" + removePipStack + ", current state="
+                            + getStateDescription());
+        }
+
         mState = STATE_NO_PIP;
         mPipTaskId = TASK_ID_NO_PIP;
         mPipMediaController = null;
@@ -371,6 +386,8 @@
      * Moves the PIPed activity to the fullscreen and closes PIP system UI.
      */
     void movePipToFullscreen() {
+        if (DEBUG) Log.d(TAG, "movePipToFullscreen(), current state=" + getStateDescription());
+
         mPipTaskId = TASK_ID_NO_PIP;
         for (int i = mListeners.size() - 1; i >= 0; --i) {
             mListeners.get(i).onMoveToFullscreen();
@@ -386,6 +403,7 @@
     public void suspendPipResizing(int reason) {
         if (DEBUG) Log.d(TAG,
                 "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
+
         mSuspendPipResizingReason |= reason;
     }
 
@@ -408,7 +426,11 @@
      * @param state In Pip state also used to determine the new size for the Pip.
      */
     void resizePinnedStack(int state) {
-        if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state, new Exception());
+        if (DEBUG) {
+            Log.d(TAG, "resizePinnedStack() state=" + stateToName(state) + ", current state="
+                    + getStateDescription(), new Exception());
+        }
+
         boolean wasStateNoPip = (mState == STATE_NO_PIP);
         for (int i = mListeners.size() - 1; i >= 0; --i) {
             mListeners.get(i).onPipResizeAboutToStart();
@@ -418,7 +440,7 @@
             if (DEBUG) Log.d(TAG, "resizePinnedStack() deferring"
                     + " mSuspendPipResizingReason=" + mSuspendPipResizingReason
                     + " mResumeResizePinnedStackRunnableState="
-                    + mResumeResizePinnedStackRunnableState);
+                    + stateToName(mResumeResizePinnedStackRunnableState));
             return;
         }
         mState = state;
@@ -458,7 +480,8 @@
      * stack to the centered PIP bound {@link R.config_centeredPictureInPictureBounds}.
      */
     private void showPipMenu() {
-        if (DEBUG) Log.d(TAG, "showPipMenu()");
+        if (DEBUG) Log.d(TAG, "showPipMenu(), current state=" + getStateDescription());
+
         mState = STATE_PIP_MENU;
         for (int i = mListeners.size() - 1; i >= 0; --i) {
             mListeners.get(i).onShowPipMenu();
@@ -712,6 +735,7 @@
 
     private void onPipTransitionFinishedOrCanceled() {
         if (DEBUG) Log.d(TAG, "onPipTransitionFinishedOrCanceled()");
+
         if (getState() == STATE_PIP_MENU) {
             showPipMenu();
         }
@@ -753,4 +777,28 @@
             WindowManagerWrapper.getInstance().setPipVisibility(visible);
         });
     }
+
+    private String getStateDescription() {
+        if (mSuspendPipResizingReason == 0) {
+            return stateToName(mState);
+        }
+        return stateToName(mResumeResizePinnedStackRunnableState) + " (while " + stateToName(mState)
+                + " is suspended)";
+    }
+
+    private static String stateToName(int state) {
+        switch (state) {
+            case STATE_NO_PIP:
+                return "NO_PIP";
+
+            case STATE_PIP:
+                return "PIP";
+
+            case STATE_PIP_MENU:
+                return "PIP_MENU";
+
+            default:
+                return "UNKNOWN(" + state + ")";
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
index f43f8e7..c7e77cc 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.pm.ParceledListSlice;
 import android.os.Bundle;
+import android.util.Log;
 
 import com.android.systemui.R;
 import com.android.systemui.pip.tv.dagger.TvPipComponent;
@@ -34,6 +35,7 @@
  * Activity to show the PIP menu to control PIP.
  */
 public class PipMenuActivity extends Activity implements PipManager.Listener {
+    private static final boolean DEBUG = false;
     private static final String TAG = "PipMenuActivity";
 
     static final String EXTRA_CUSTOM_ACTIONS = "custom_actions";
@@ -47,7 +49,6 @@
     private boolean mRestorePipSizeWhenClose;
     private PipControlsViewController mPipControlsViewController;
 
-
     @Inject
     public PipMenuActivity(TvPipComponent.Builder pipComponentBuilder, PipManager pipManager) {
         super();
@@ -57,6 +58,8 @@
 
     @Override
     protected void onCreate(Bundle bundle) {
+        if (DEBUG) Log.d(TAG, "onCreate()");
+
         super.onCreate(bundle);
         if (!mPipManager.isPipShown()) {
             finish();
@@ -81,13 +84,18 @@
 
     @Override
     protected void onNewIntent(Intent intent) {
+        if (DEBUG) Log.d(TAG, "onNewIntent(), intent=" + intent);
         super.onNewIntent(intent);
 
         onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS));
     }
 
     private void restorePipAndFinish() {
+        if (DEBUG) Log.d(TAG, "restorePipAndFinish()");
+
         if (mRestorePipSizeWhenClose) {
+            if (DEBUG) Log.d(TAG, "   > restoring to the default position");
+
             // When PIP menu activity is closed, restore to the default position.
             mPipManager.resizePinnedStack(PipManager.STATE_PIP);
         }
@@ -96,12 +104,16 @@
 
     @Override
     public void onResume() {
+        if (DEBUG) Log.d(TAG, "onResume()");
+
         super.onResume();
         mFadeInAnimation.start();
     }
 
     @Override
     public void onPause() {
+        if (DEBUG) Log.d(TAG, "onPause()");
+
         super.onPause();
         mFadeOutAnimation.start();
         restorePipAndFinish();
@@ -109,6 +121,8 @@
 
     @Override
     protected void onDestroy() {
+        if (DEBUG) Log.d(TAG, "onDestroy()");
+
         super.onDestroy();
         mPipManager.removeListener(this);
         mPipManager.resumePipResizing(
@@ -117,29 +131,41 @@
 
     @Override
     public void onBackPressed() {
+        if (DEBUG) Log.d(TAG, "onBackPressed()");
+
         restorePipAndFinish();
     }
 
     @Override
-    public void onPipEntered() { }
+    public void onPipEntered() {
+        if (DEBUG) Log.d(TAG, "onPipEntered()");
+    }
 
     @Override
     public void onPipActivityClosed() {
+        if (DEBUG) Log.d(TAG, "onPipActivityClosed()");
+
         finish();
     }
 
     @Override
     public void onPipMenuActionsChanged(ParceledListSlice actions) {
+        if (DEBUG) Log.d(TAG, "onPipMenuActionsChanged()");
+
         boolean hasCustomActions = actions != null && !actions.getList().isEmpty();
         mPipControlsViewController.setActions(
                 hasCustomActions ? actions.getList() : Collections.EMPTY_LIST);
     }
 
     @Override
-    public void onShowPipMenu() { }
+    public void onShowPipMenu() {
+        if (DEBUG) Log.d(TAG, "onShowPipMenu()");
+    }
 
     @Override
     public void onMoveToFullscreen() {
+        if (DEBUG) Log.d(TAG, "onMoveToFullscreen()");
+
         // Moving PIP to fullscreen is implemented by resizing PINNED_STACK with null bounds.
         // This conflicts with restoring PIP position, so disable it.
         mRestorePipSizeWhenClose = false;
@@ -148,8 +174,17 @@
 
     @Override
     public void onPipResizeAboutToStart() {
+        if (DEBUG) Log.d(TAG, "onPipResizeAboutToStart()");
+
         finish();
         mPipManager.suspendPipResizing(
                 PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
     }
+
+    @Override
+    public void finish() {
+        if (DEBUG) Log.d(TAG, "finish()", new RuntimeException());
+
+        super.finish();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index e1ffad4..90dc38f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -22,10 +22,8 @@
 import android.annotation.ColorInt;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -49,13 +47,16 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
 
 import com.android.settingslib.Utils;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.DualToneHandler;
 import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
@@ -70,6 +71,7 @@
 import com.android.systemui.statusbar.policy.DateView;
 import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.util.RingerModeTracker;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -86,7 +88,7 @@
  */
 public class QuickStatusBarHeader extends RelativeLayout implements
         View.OnClickListener, NextAlarmController.NextAlarmChangeCallback,
-        ZenModeController.Callback {
+        ZenModeController.Callback, LifecycleOwner {
     private static final String TAG = "QuickStatusBarHeader";
     private static final boolean DEBUG = false;
 
@@ -137,15 +139,9 @@
     private DateView mDateView;
     private BatteryMeterView mBatteryRemainingIcon;
 
-    private BroadcastDispatcher mBroadcastDispatcher;
+    // Used for RingerModeTracker
+    private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
 
-    private final BroadcastReceiver mRingerReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            mRingerMode = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
-            updateStatusText();
-        }
-    };
     private boolean mHasTopCutout = false;
     private int mRoundedCornerPadding = 0;
 
@@ -154,7 +150,7 @@
             NextAlarmController nextAlarmController, ZenModeController zenModeController,
             StatusBarIconController statusBarIconController,
             ActivityStarter activityStarter,
-            CommandQueue commandQueue, BroadcastDispatcher broadcastDispatcher) {
+            CommandQueue commandQueue, RingerModeTracker ringerModeTracker) {
         super(context, attrs);
         mAlarmController = nextAlarmController;
         mZenController = zenModeController;
@@ -162,8 +158,11 @@
         mActivityStarter = activityStarter;
         mDualToneHandler = new DualToneHandler(
                 new ContextThemeWrapper(context, R.style.QSHeaderTheme));
-        mBroadcastDispatcher = broadcastDispatcher;
         mCommandQueue = commandQueue;
+        ringerModeTracker.getRingerModeInternal().observe(this, ringer -> {
+            mRingerMode = ringer;
+            updateStatusText();
+        });
     }
 
     @Override
@@ -484,12 +483,11 @@
         if (listening) {
             mZenController.addCallback(this);
             mAlarmController.addCallback(this);
-            mBroadcastDispatcher.registerReceiver(mRingerReceiver,
-                    new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
+            mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
         } else {
             mZenController.removeCallback(this);
             mAlarmController.removeCallback(this);
-            mBroadcastDispatcher.unregisterReceiver(mRingerReceiver);
+            mLifecycle.setCurrentState(Lifecycle.State.DESTROYED);
         }
     }
 
@@ -586,4 +584,10 @@
             lp.rightMargin = sideMargins;
         }
     }
+
+    @NonNull
+    @Override
+    public Lifecycle getLifecycle() {
+        return mLifecycle;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
index 366ef93..2df4506 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java
@@ -1042,7 +1042,8 @@
         dockedTaskRect = dockedTaskRect == null ? dockedRect : dockedTaskRect;
         otherTaskRect = otherTaskRect == null ? otherRect : otherTaskRect;
 
-        mDividerPositionX = dockedRect.right;
+        mDividerPositionX = mSplitLayout.getPrimarySplitSide() == DOCKED_RIGHT
+                ? otherRect.right : dockedRect.right;
         mDividerPositionY = dockedRect.bottom;
 
         if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java
index 3b8addb..92f6b4a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java
@@ -101,7 +101,16 @@
     }
 
     int getPrimarySplitSide() {
-        return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP;
+        switch (mDisplayLayout.getNavigationBarPosition(mContext.getResources())) {
+            case DisplayLayout.NAV_BAR_BOTTOM:
+                return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP;
+            case DisplayLayout.NAV_BAR_LEFT:
+                return DOCKED_RIGHT;
+            case DisplayLayout.NAV_BAR_RIGHT:
+                return DOCKED_LEFT;
+            default:
+                return DOCKED_INVALID;
+        }
     }
 
     boolean isMinimized() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index d2da262..729f934 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -21,6 +21,7 @@
 import android.animation.ValueAnimator
 import android.app.WallpaperManager
 import android.util.Log
+import android.util.MathUtils
 import android.view.Choreographer
 import android.view.View
 import androidx.annotation.VisibleForTesting
@@ -37,6 +38,7 @@
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController
 import com.android.systemui.statusbar.phone.PanelExpansionListener
+import com.android.systemui.statusbar.phone.ScrimController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import java.io.FileDescriptor
 import java.io.PrintWriter
@@ -106,6 +108,16 @@
         }
 
     /**
+     * Force stop blur effect when necessary.
+     */
+    private var scrimsVisible: Boolean = false
+        set(value) {
+            if (field == value) return
+            field = value
+            scheduleUpdate()
+        }
+
+    /**
      * Blur radius of the wake-up animation on this frame.
      */
     private var wakeAndUnlockBlurRadius = 0
@@ -141,7 +153,13 @@
         if (showingHomeControls) {
             globalActionsRadius = 0
         }
-        val blur = max(shadeRadius.toInt(), globalActionsRadius)
+        var blur = max(shadeRadius.toInt(), globalActionsRadius)
+
+        // Make blur be 0 if it is necessary to stop blur effect.
+        if (scrimsVisible) {
+            blur = 0
+        }
+
         blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur)
         try {
             wallpaperManager.setWallpaperZoomOut(root.windowToken,
@@ -201,6 +219,10 @@
                 brightnessMirrorSpring.finishIfRunning()
             }
         }
+
+        override fun onDozeAmountChanged(linear: Float, eased: Float) {
+            wakeAndUnlockBlurRadius = blurUtils.blurRadiusOfRatio(eased)
+        }
     }
 
     init {
@@ -209,6 +231,10 @@
             keyguardStateController.addCallback(keyguardStateCallback)
         }
         statusBarStateController.addCallback(statusBarStateCallback)
+        notificationShadeWindowController.setScrimsVisibilityListener {
+            // Stop blur effect when scrims is opaque to avoid unnecessary GPU composition.
+            visibility -> scrimsVisible = visibility == ScrimController.OPAQUE
+        }
     }
 
     /**
@@ -224,8 +250,12 @@
 
     private fun updateShadeBlur() {
         var newBlur = 0
-        if (statusBarStateController.state == StatusBarState.SHADE) {
-            newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
+        val state = statusBarStateController.state
+        if (state == StatusBarState.SHADE || state == StatusBarState.SHADE_LOCKED) {
+            val animatedBlur =
+                    Interpolators.SHADE_ANIMATION.getInterpolation(
+                            MathUtils.constrain(shadeExpansion / 0.15f, 0f, 1f))
+            newBlur = blurUtils.blurRadiusOfRatio(0.35f * animatedBlur + 0.65f * shadeExpansion)
         }
         shadeSpring.animateTo(newBlur)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index c8b34f1..4e6df0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -75,6 +75,9 @@
             // We never want to open the app directly if the user clicks in between
             // the notifications.
             return;
+        } else if (row.areGutsExposed()) {
+            // ignore click if guts are exposed
+            return;
         }
 
         // Mark notification for one frame.
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 295adae..9324b14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
+import android.os.SystemClock;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
 import android.service.notification.NotificationListenerService.RankingMap;
@@ -555,8 +556,8 @@
         NotificationEntry entry = new NotificationEntry(
                 notification,
                 ranking,
-                mFgsFeatureController.isForegroundServiceDismissalEnabled());
-        mAllNotifications.add(entry);
+                mFgsFeatureController.isForegroundServiceDismissalEnabled(),
+                SystemClock.uptimeMillis());
 
         mLeakDetector.trackInstance(entry);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 9c2cac71..365862b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -47,6 +47,7 @@
 import android.annotation.UserIdInt;
 import android.app.Notification;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
@@ -387,7 +388,7 @@
 
         if (entry == null) {
             // A new notification!
-            entry = new NotificationEntry(sbn, ranking);
+            entry = new NotificationEntry(sbn, ranking, SystemClock.uptimeMillis());
             mNotificationSet.put(sbn.getKey(), entry);
 
             mLogger.logNotifPosted(sbn.getKey());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index c1ba26d..68ec34e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -35,6 +35,7 @@
 
 import static java.util.Objects.requireNonNull;
 
+import android.annotation.CurrentTimeMillisLong;
 import android.app.Notification;
 import android.app.Notification.MessagingStyle.Message;
 import android.app.NotificationChannel;
@@ -93,6 +94,7 @@
     private final String mKey;
     private StatusBarNotification mSbn;
     private Ranking mRanking;
+    private long mCreationTime;
 
     /*
      * Bookkeeping members
@@ -171,21 +173,29 @@
     private boolean mAllowFgsDismissal;
     private int mBucket = BUCKET_ALERTING;
 
+    /**
+     * @param sbn the StatusBarNotification from system server
+     * @param ranking also from system server
+     * @param creationTime SystemClock.uptimeMillis of when we were created
+     */
     public NotificationEntry(
             @NonNull StatusBarNotification sbn,
-            @NonNull Ranking ranking) {
-        this(sbn, ranking, false);
+            @NonNull Ranking ranking,
+            long creationTime) {
+        this(sbn, ranking, false, creationTime);
     }
 
     public NotificationEntry(
             @NonNull StatusBarNotification sbn,
             @NonNull Ranking ranking,
-            boolean allowFgsDismissal
+            boolean allowFgsDismissal,
+            long creationTime
     ) {
         super(requireNonNull(Objects.requireNonNull(sbn).getKey()));
 
         requireNonNull(ranking);
 
+        mCreationTime = creationTime;
         mKey = sbn.getKey();
         setSbn(sbn);
         setRanking(ranking);
@@ -238,6 +248,21 @@
     }
 
     /**
+     * A timestamp of SystemClock.uptimeMillis() of when this entry was first created, regardless
+     * of any changes to the data presented. It is set once on creation and will never change, and
+     * allows us to know exactly how long this notification has been alive for in our listener
+     * service. It is entirely unrelated to the information inside of the notification.
+     *
+     * This is different to Notification#when because it persists throughout updates, whereas
+     * system server treats every single call to notify() as a new notification and we handle
+     * updates to NotificationEntry locally.
+     */
+    @CurrentTimeMillisLong
+    public long getCreationTime() {
+        return mCreationTime;
+    }
+
+    /**
      * Should only be called by NotificationEntryManager and friends.
      * TODO: Make this package-private
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 5205bab..7ac0662 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -169,7 +169,7 @@
     @Override
     public void onContentUpdated(ExpandableNotificationRow row) {
         super.onContentUpdated(row);
-        mIsLowPriority = row.isLowPriority();
+        mIsLowPriority = row.getEntry().isAmbient();
         mTransformLowPriorityTitle = !row.isChildInGroup() && !row.isSummaryWithChildren();
         ArraySet<View> previousViews = mTransformationHelper.getAllTransformingViews();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
index e1e679f..462b42a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
@@ -61,6 +61,7 @@
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -92,6 +93,7 @@
     private final State mCurrentState = new State();
     private OtherwisedCollapsedListener mListener;
     private ForcePluginOpenListener mForcePluginOpenListener;
+    private Consumer<Integer> mScrimsVisibilityListener;
     private final ArrayList<WeakReference<StatusBarWindowCallback>>
             mCallbacks = Lists.newArrayList();
 
@@ -150,6 +152,16 @@
         mCallbacks.add(new WeakReference<StatusBarWindowCallback>(callback));
     }
 
+    /**
+     * Register a listener to monitor scrims visibility
+     * @param listener A listener to monitor scrims visibility
+     */
+    public void setScrimsVisibilityListener(Consumer<Integer> listener) {
+        if (listener != null && mScrimsVisibilityListener != listener) {
+            mScrimsVisibilityListener = listener;
+        }
+    }
+
     private boolean shouldEnableKeyguardScreenRotation() {
         Resources res = mContext.getResources();
         return SystemProperties.getBoolean("lockscreen.rot_override", false)
@@ -477,6 +489,7 @@
     public void setScrimsVisibility(int scrimsVisibility) {
         mCurrentState.mScrimsVisibility = scrimsVisibility;
         apply(mCurrentState);
+        mScrimsVisibilityListener.accept(scrimsVisibility);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 14af466..d4f4d3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -39,6 +39,8 @@
 import android.text.format.DateFormat;
 import android.util.Log;
 
+import androidx.lifecycle.Observer;
+
 import com.android.systemui.R;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.qualifiers.DisplayId;
@@ -64,6 +66,7 @@
 import com.android.systemui.statusbar.policy.SensorPrivacyController;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.time.DateFormatUtil;
 
 import java.util.Locale;
@@ -109,7 +112,6 @@
     private final SharedPreferences mSharedPreferences;
     private final DateFormatUtil mDateFormatUtil;
     private final TelecomManager mTelecomManager;
-    private final AudioManager mAudioManager;
 
     private final Handler mHandler = new Handler();
     private final CastController mCast;
@@ -132,6 +134,7 @@
     private final Executor mUiBgExecutor;
     private final SensorPrivacyController mSensorPrivacyController;
     private final RecordingController mRecordingController;
+    private final RingerModeTracker mRingerModeTracker;
 
     private boolean mZenVisible;
     private boolean mVolumeVisible;
@@ -154,10 +157,11 @@
             KeyguardStateController keyguardStateController,
             LocationController locationController,
             SensorPrivacyController sensorPrivacyController, IActivityManager iActivityManager,
-            AlarmManager alarmManager, UserManager userManager, AudioManager audioManager,
+            AlarmManager alarmManager, UserManager userManager,
             RecordingController recordingController,
             @Nullable TelecomManager telecomManager, @DisplayId int displayId,
-            @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil) {
+            @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
+            RingerModeTracker ringerModeTracker) {
         mIconController = iconController;
         mCommandQueue = commandQueue;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -179,8 +183,8 @@
         mSensorPrivacyController = sensorPrivacyController;
         mRecordingController = recordingController;
         mUiBgExecutor = uiBgExecutor;
-        mAudioManager = audioManager;
         mTelecomManager = telecomManager;
+        mRingerModeTracker = ringerModeTracker;
 
         mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
         mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -208,8 +212,7 @@
     public void init() {
         // listen for broadcasts
         IntentFilter filter = new IntentFilter();
-        filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
-        filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
+
         filter.addAction(AudioManager.ACTION_HEADSET_PLUG);
         filter.addAction(Intent.ACTION_SIM_STATE_CHANGED);
         filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
@@ -217,6 +220,10 @@
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
         filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
         mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
+        Observer<Integer> observer = ringer -> mHandler.post(this::updateVolumeZen);
+
+        mRingerModeTracker.getRingerMode().observeForever(observer);
+        mRingerModeTracker.getRingerModeInternal().observeForever(observer);
 
         // listen for user / profile change.
         try {
@@ -350,14 +357,18 @@
         }
 
         if (!ZenModeConfig.isZenOverridingRinger(zen, mZenController.getConsolidatedPolicy())) {
-            if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_VIBRATE) {
-                volumeVisible = true;
-                volumeIconId = R.drawable.stat_sys_ringer_vibrate;
-                volumeDescription = mResources.getString(R.string.accessibility_ringer_vibrate);
-            } else if (mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT) {
-                volumeVisible = true;
-                volumeIconId = R.drawable.stat_sys_ringer_silent;
-                volumeDescription = mResources.getString(R.string.accessibility_ringer_silent);
+            final Integer ringerModeInternal =
+                    mRingerModeTracker.getRingerModeInternal().getValue();
+            if (ringerModeInternal != null) {
+                if (ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
+                    volumeVisible = true;
+                    volumeIconId = R.drawable.stat_sys_ringer_vibrate;
+                    volumeDescription = mResources.getString(R.string.accessibility_ringer_vibrate);
+                } else if (ringerModeInternal == AudioManager.RINGER_MODE_SILENT) {
+                    volumeVisible = true;
+                    volumeIconId = R.drawable.stat_sys_ringer_silent;
+                    volumeDescription = mResources.getString(R.string.accessibility_ringer_silent);
+                }
             }
         }
 
@@ -616,10 +627,6 @@
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
             switch (action) {
-                case AudioManager.RINGER_MODE_CHANGED_ACTION:
-                case AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION:
-                    updateVolumeZen();
-                    break;
                 case Intent.ACTION_SIM_STATE_CHANGED:
                     // Avoid rebroadcast because SysUI is direct boot aware.
                     if (intent.getBooleanExtra(Intent.EXTRA_REBROADCAST_ON_UNLOCK, false)) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
new file mode 100644
index 0000000..d65b285
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
@@ -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.systemui.util
+
+import android.graphics.PointF
+import android.os.Handler
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import android.view.View
+import android.view.ViewConfiguration
+import kotlin.math.hypot
+
+/**
+ * Listener which receives [onDown], [onMove], and [onUp] events, with relevant information about
+ * the coordinates of the touch and the view relative to the initial ACTION_DOWN event and the
+ * view's initial position.
+ */
+abstract class RelativeTouchListener : View.OnTouchListener {
+
+    /**
+     * Called when an ACTION_DOWN event is received for the given view.
+     *
+     * @return False if the object is not interested in MotionEvents at this time, or true if we
+     * should consume this event and subsequent events, and begin calling [onMove].
+     */
+    abstract fun onDown(v: View, ev: MotionEvent): Boolean
+
+    /**
+     * Called when an ACTION_MOVE event is received for the given view. This signals that the view
+     * is being dragged.
+     *
+     * @param viewInitialX The view's translationX value when this touch gesture started.
+     * @param viewInitialY The view's translationY value when this touch gesture started.
+     * @param dx Horizontal distance covered since the initial ACTION_DOWN event, in pixels.
+     * @param dy Vertical distance covered since the initial ACTION_DOWN event, in pixels.
+     */
+    abstract fun onMove(
+        v: View,
+        ev: MotionEvent,
+        viewInitialX: Float,
+        viewInitialY: Float,
+        dx: Float,
+        dy: Float
+    )
+
+    /**
+     * Called when an ACTION_UP event is received for the given view. This signals that a drag or
+     * fling gesture has completed.
+     *
+     * @param viewInitialX The view's translationX value when this touch gesture started.
+     * @param viewInitialY The view's translationY value when this touch gesture started.
+     * @param dx Horizontal distance covered, in pixels.
+     * @param dy Vertical distance covered, in pixels.
+     * @param velX The final horizontal velocity of the gesture, in pixels/second.
+     * @param velY The final vertical velocity of the gesture, in pixels/second.
+     */
+    abstract fun onUp(
+        v: View,
+        ev: MotionEvent,
+        viewInitialX: Float,
+        viewInitialY: Float,
+        dx: Float,
+        dy: Float,
+        velX: Float,
+        velY: Float
+    )
+
+    /** The raw coordinates of the last ACTION_DOWN event. */
+    private val touchDown = PointF()
+
+    /** The coordinates of the view, at the time of the last ACTION_DOWN event. */
+    private val viewPositionOnTouchDown = PointF()
+
+    private val velocityTracker = VelocityTracker.obtain()
+
+    private var touchSlop: Int = -1
+    private var movedEnough = false
+
+    private val handler = Handler()
+    private var performedLongClick = false
+
+    @Suppress("UNCHECKED_CAST")
+    override fun onTouch(v: View, ev: MotionEvent): Boolean {
+        addMovement(ev)
+
+        val dx = ev.rawX - touchDown.x
+        val dy = ev.rawY - touchDown.y
+
+        when (ev.action) {
+            MotionEvent.ACTION_DOWN -> {
+                if (!onDown(v, ev)) {
+                    return false
+                }
+
+                // Grab the touch slop, it might have changed if the config changed since the
+                // last gesture.
+                touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop
+
+                touchDown.set(ev.rawX, ev.rawY)
+                viewPositionOnTouchDown.set(v.translationX, v.translationY)
+
+                performedLongClick = false
+                handler.postDelayed({
+                    performedLongClick = v.performLongClick()
+                }, ViewConfiguration.getLongPressTimeout().toLong())
+            }
+
+            MotionEvent.ACTION_MOVE -> {
+                if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
+                    movedEnough = true
+                    handler.removeCallbacksAndMessages(null)
+                }
+
+                if (movedEnough) {
+                    onMove(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy)
+                }
+            }
+
+            MotionEvent.ACTION_UP -> {
+                if (movedEnough) {
+                    velocityTracker.computeCurrentVelocity(1000 /* units */)
+                    onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy,
+                            velocityTracker.xVelocity, velocityTracker.yVelocity)
+                } else if (!performedLongClick) {
+                    v.performClick()
+                } else {
+                    handler.removeCallbacksAndMessages(null)
+                }
+
+                velocityTracker.clear()
+                movedEnough = false
+            }
+        }
+
+        return true
+    }
+
+    /**
+     * Adds a movement to the velocity tracker using raw screen coordinates.
+     */
+    private fun addMovement(event: MotionEvent) {
+        val deltaX = event.rawX - event.x
+        val deltaY = event.rawY - event.y
+        event.offsetLocation(deltaX, deltaY)
+        velocityTracker.addMovement(event)
+        event.offsetLocation(-deltaX, -deltaY)
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/RingerModeTracker.kt b/packages/SystemUI/src/com/android/systemui/util/RingerModeTracker.kt
new file mode 100644
index 0000000..047298e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/RingerModeTracker.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import androidx.lifecycle.LiveData
+
+interface RingerModeTracker {
+
+    val ringerMode: LiveData<Int>
+    val ringerModeInternal: LiveData<Int>
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt
new file mode 100644
index 0000000..58684c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.AudioManager
+import android.os.UserHandle
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Background
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class RingerModeTrackerImpl @Inject constructor(
+    audioManager: AudioManager,
+    broadcastDispatcher: BroadcastDispatcher,
+    @Background executor: Executor
+) : RingerModeTracker {
+
+    override val ringerMode: LiveData<Int> = RingerModeLiveData(
+            broadcastDispatcher,
+            executor,
+            AudioManager.RINGER_MODE_CHANGED_ACTION,
+            audioManager::getRingerMode
+    )
+
+    override val ringerModeInternal: LiveData<Int> = RingerModeLiveData(
+            broadcastDispatcher,
+            executor,
+            AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION,
+            audioManager::getRingerModeInternal
+    )
+}
+
+class RingerModeLiveData(
+    private val broadcastDispatcher: BroadcastDispatcher,
+    private val executor: Executor,
+    intent: String,
+    private val getter: () -> Int
+) : MutableLiveData<Int>() {
+
+    private val filter = IntentFilter(intent)
+    var initialSticky: Boolean = false
+        private set
+
+    private val receiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context, intent: Intent) {
+            initialSticky = isInitialStickyBroadcast
+            postValue(intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1))
+        }
+    }
+
+    override fun getValue(): Int {
+        return super.getValue() ?: -1
+    }
+
+    override fun onActive() {
+        super.onActive()
+        broadcastDispatcher.registerReceiver(receiver, filter, executor, UserHandle.ALL)
+        executor.execute {
+            postValue(getter())
+        }
+    }
+
+    override fun onInactive() {
+        super.onInactive()
+        broadcastDispatcher.unregisterReceiver(receiver)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
index 812a1e4..f27bdbf 100644
--- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
@@ -160,6 +160,18 @@
     lateinit var magnetListener: MagnetizedObject.MagnetListener
 
     /**
+     * Optional update listener to provide to the PhysicsAnimator that is used to spring the object
+     * into the target.
+     */
+    var physicsAnimatorUpdateListener: PhysicsAnimator.UpdateListener<T>? = null
+
+    /**
+     * Optional end listener to provide to the PhysicsAnimator that is used to spring the object
+     * into the target.
+     */
+    var physicsAnimatorEndListener: PhysicsAnimator.EndListener<T>? = null
+
+    /**
      * Sets whether forcefully flinging the object vertically towards a target causes it to be
      * attracted to the target and then released immediately, despite never being dragged within the
      * magnetic field.
@@ -479,6 +491,14 @@
                 .spring(yProperty, yProperty.getValue(underlyingObject) + yDiff, velY,
                         springConfig)
 
+        if (physicsAnimatorUpdateListener != null) {
+            animator.addUpdateListener(physicsAnimatorUpdateListener!!)
+        }
+
+        if (physicsAnimatorEndListener != null) {
+            animator.addEndListener(physicsAnimatorEndListener!!)
+        }
+
         if (after != null) {
             animator.withEndActions(after)
         }
@@ -560,13 +580,15 @@
         private val tempLoc = IntArray(2)
 
         fun updateLocationOnScreen() {
-            targetView.getLocationOnScreen(tempLoc)
+            targetView.post {
+                targetView.getLocationOnScreen(tempLoc)
 
-            // Add half of the target size to get the center, and subtract translation since the
-            // target could be animating in while we're doing this calculation.
-            centerOnScreen.set(
-                    tempLoc[0] + targetView.width / 2f - targetView.translationX,
-                    tempLoc[1] + targetView.height / 2f - targetView.translationY)
+                // Add half of the target size to get the center, and subtract translation since the
+                // target could be animating in while we're doing this calculation.
+                centerOnScreen.set(
+                        tempLoc[0] + targetView.width / 2f - targetView.translationX,
+                        tempLoc[1] + targetView.height / 2f - targetView.translationY)
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 7353263..f19c49c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -55,6 +55,8 @@
 import android.util.Slog;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.lifecycle.Observer;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.settingslib.volume.MediaSessions;
 import com.android.systemui.Dumpable;
@@ -64,6 +66,8 @@
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.util.RingerModeLiveData;
+import com.android.systemui.util.RingerModeTracker;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -122,6 +126,7 @@
     private final NotificationManager mNoMan;
     private final SettingObserver mObserver;
     private final Receiver mReceiver = new Receiver();
+    private final RingerModeObservers mRingerModeObservers;
     private final MediaSessions mMediaSessions;
     protected C mCallbacks = new C();
     private final State mState = new State();
@@ -145,7 +150,7 @@
 
     @Inject
     public VolumeDialogControllerImpl(Context context, BroadcastDispatcher broadcastDispatcher,
-            Optional<Lazy<StatusBar>> statusBarOptionalLazy) {
+            Optional<Lazy<StatusBar>> statusBarOptionalLazy, RingerModeTracker ringerModeTracker) {
         mContext = context.getApplicationContext();
         // TODO(b/150663459): remove this TV workaround once StatusBar is "unbound" on TVs
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
@@ -164,6 +169,11 @@
         mAudio = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         mNoMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
         mObserver = new SettingObserver(mWorker);
+        mRingerModeObservers = new RingerModeObservers(
+                (RingerModeLiveData) ringerModeTracker.getRingerMode(),
+                (RingerModeLiveData) ringerModeTracker.getRingerModeInternal()
+        );
+        mRingerModeObservers.init();
         mBroadcastDispatcher = broadcastDispatcher;
         mObserver.init();
         mReceiver.init();
@@ -246,6 +256,7 @@
         mMediaSessions.destroy();
         mObserver.destroy();
         mReceiver.destroy();
+        mRingerModeObservers.destroy();
         mWorkerThread.quitSafely();
     }
 
@@ -528,7 +539,8 @@
             ss.name = STREAMS.get(stream);
             checkRoutedToBluetoothW(stream);
         }
-        updateRingerModeExternalW(mAudio.getRingerMode());
+        // We are not destroyed so this is listening and has updated information
+        updateRingerModeExternalW(mRingerModeObservers.mRingerMode.getValue());
         updateZenModeW();
         updateZenConfig();
         updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
@@ -575,7 +587,7 @@
             Events.writeEvent(Events.EVENT_MUTE_CHANGED, stream, muted);
         }
         if (muted && isRinger(stream)) {
-            updateRingerModeInternalW(mAudio.getRingerModeInternal());
+            updateRingerModeInternalW(mRingerModeObservers.mRingerModeInternal.getValue());
         }
         return true;
     }
@@ -964,6 +976,79 @@
         }
     }
 
+    private final class RingerModeObservers {
+
+        private final RingerModeLiveData mRingerMode;
+        private final RingerModeLiveData mRingerModeInternal;
+
+        private final Observer<Integer> mRingerModeObserver = new Observer<Integer>() {
+            @Override
+            public void onChanged(Integer value) {
+                mWorker.post(() -> {
+                            final int rm = value;
+                            if (mRingerMode.getInitialSticky()) {
+                                mState.ringerModeExternal = rm;
+                            }
+                            if (D.BUG) {
+                                Log.d(TAG, "onChange ringer_mode rm="
+                                        + Util.ringerModeToString(rm));
+                            }
+                            if (updateRingerModeExternalW(rm)) {
+                                mCallbacks.onStateChanged(mState);
+                            }
+                        }
+                );
+            }
+        };
+
+        private final Observer<Integer> mRingerModeInternalObserver = new Observer<Integer>() {
+            @Override
+            public void onChanged(Integer value) {
+                mWorker.post(() -> {
+                            final int rm = value;
+                            if (mRingerModeInternal.getInitialSticky()) {
+                                mState.ringerModeInternal = rm;
+                            }
+                            if (D.BUG) {
+                                Log.d(TAG, "onChange internal_ringer_mode rm="
+                                        + Util.ringerModeToString(rm));
+                            }
+                            if (updateRingerModeInternalW(rm)) {
+                                mCallbacks.onStateChanged(mState);
+                            }
+                        }
+                );
+            }
+        };
+
+        RingerModeObservers(RingerModeLiveData ringerMode,
+                RingerModeLiveData ringerModeInternal) {
+            mRingerMode = ringerMode;
+            mRingerModeInternal = ringerModeInternal;
+        }
+
+        public void init() {
+            int initialValue = mRingerMode.getValue();
+            if (initialValue != -1) {
+                // If it's not -1, set it to the initial value, if it's -1, it means that the
+                // tracker is not listening already and will obtain the sticky value.
+                mState.ringerModeExternal = initialValue;
+            }
+            mRingerMode.observeForever(mRingerModeObserver);
+            initialValue = mRingerModeInternal.getValue();
+            if (initialValue != -1) {
+                // If it's not -1, set it to the initial value, if it's -1, it means that the
+                // tracker is not listening already and will obtain the sticky value.
+                mState.ringerModeInternal = initialValue;
+            }
+            mRingerModeInternal.observeForever(mRingerModeInternalObserver);
+        }
+
+        public void destroy() {
+            mRingerMode.removeObserver(mRingerModeObserver);
+            mRingerModeInternal.removeObserver(mRingerModeInternalObserver);
+        }
+    }
 
     private final class SettingObserver extends ContentObserver {
         private final Uri ZEN_MODE_URI =
@@ -1006,8 +1091,6 @@
             final IntentFilter filter = new IntentFilter();
             filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
             filter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
-            filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
-            filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
             filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
             filter.addAction(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED);
             filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
@@ -1042,18 +1125,6 @@
                         + stream + " devices=" + devices + " oldDevices=" + oldDevices);
                 changed = checkRoutedToBluetoothW(stream);
                 changed |= onVolumeChangedW(stream, 0);
-            } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
-                final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
-                if (isInitialStickyBroadcast()) mState.ringerModeExternal = rm;
-                if (D.BUG) Log.d(TAG, "onReceive RINGER_MODE_CHANGED_ACTION rm="
-                        + Util.ringerModeToString(rm));
-                changed = updateRingerModeExternalW(rm);
-            } else if (action.equals(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION)) {
-                final int rm = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE, -1);
-                if (isInitialStickyBroadcast()) mState.ringerModeInternal = rm;
-                if (D.BUG) Log.d(TAG, "onReceive INTERNAL_RINGER_MODE_CHANGED_ACTION rm="
-                        + Util.ringerModeToString(rm));
-                changed = updateRingerModeInternalW(rm);
             } else if (action.equals(AudioManager.STREAM_MUTE_CHANGED_ACTION)) {
                 final int stream = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
                 final boolean muted = intent
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
index 4652abf..cfec1c0 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
@@ -27,6 +27,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -46,16 +47,27 @@
 
 import com.android.internal.R;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Contains information about the layout-properties of a display. This refers to internal layout
  * like insets/cutout/rotation. In general, this can be thought of as the System-UI analog to
  * DisplayPolicy.
  */
 public class DisplayLayout {
+    @IntDef(prefix = { "NAV_BAR_" }, value = {
+            NAV_BAR_LEFT,
+            NAV_BAR_RIGHT,
+            NAV_BAR_BOTTOM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NavBarPosition {}
+
     // Navigation bar position values
-    private static final int NAV_BAR_LEFT = 1 << 0;
-    private static final int NAV_BAR_RIGHT = 1 << 1;
-    private static final int NAV_BAR_BOTTOM = 1 << 2;
+    public static final int NAV_BAR_LEFT = 1 << 0;
+    public static final int NAV_BAR_RIGHT = 1 << 1;
+    public static final int NAV_BAR_BOTTOM = 1 << 2;
 
     private int mUiMode;
     private int mWidth;
@@ -214,6 +226,14 @@
     }
 
     /**
+     * Gets navigation bar position for this layout
+     * @return Navigation bar position for this layout.
+     */
+    public @NavBarPosition int getNavigationBarPosition(Resources res) {
+        return navigationBarPosition(res, mWidth, mHeight, mRotation);
+    }
+
+    /**
      * Rotates bounds as if parentBounds and bounds are a group. The group is rotated by `delta`
      * 90-degree counter-clockwise increments. This assumes that parentBounds is at 0,0 and
      * remains at 0,0 after rotation.
@@ -437,8 +457,8 @@
     }
 
     /** Retrieve navigation bar position from resources based on rotation and size. */
-    public static int navigationBarPosition(Resources res, int displayWidth, int displayHeight,
-            int rotation) {
+    public static @NavBarPosition int navigationBarPosition(Resources res, int displayWidth,
+            int displayHeight, int rotation) {
         boolean navBarCanMove = displayWidth != displayHeight && res.getBoolean(
                 com.android.internal.R.bool.config_navBarCanMove);
         if (navBarCanMove && displayWidth > displayHeight) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
index cf1299f..ce032c9 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/AdminSecondaryLockScreenControllerTest.java
@@ -115,6 +115,7 @@
 
     @Test
     public void testShow_dismissedByCallback() throws Exception {
+        doAnswer(answerVoid(Runnable::run)).when(mHandler).post(any(Runnable.class));
         doAnswer(invocation -> {
             IKeyguardCallback callback = (IKeyguardCallback) invocation.getArguments()[1];
             callback.onDismiss();
@@ -184,7 +185,7 @@
 
     private void verifyViewDismissed(SurfaceView v) throws Exception {
         verify(mParent).removeView(v);
-        verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID);
+        verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID, true);
         assertThat(mContext.isBound(mComponentName)).isFalse();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
index 072bc44..4bcf917 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt
@@ -16,8 +16,12 @@
 
 package com.android.keyguard
 
+import android.app.Notification
 import android.graphics.drawable.Icon
 import android.media.MediaMetadata
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.media.session.PlaybackState
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.View
@@ -28,7 +32,9 @@
 
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.media.MediaControllerFactory
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -38,6 +44,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.any
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
@@ -48,9 +55,12 @@
 public class KeyguardMediaPlayerTest : SysuiTestCase() {
 
     private lateinit var keyguardMediaPlayer: KeyguardMediaPlayer
+    @Mock private lateinit var mockMediaFactory: MediaControllerFactory
+    @Mock private lateinit var mockMediaController: MediaController
+    private lateinit var playbackState: PlaybackState
     private lateinit var fakeExecutor: FakeExecutor
     private lateinit var mediaMetadata: MediaMetadata.Builder
-    private lateinit var entry: NotificationEntryBuilder
+    private lateinit var entry: NotificationEntry
     @Mock private lateinit var mockView: View
     private lateinit var songView: TextView
     private lateinit var artistView: TextView
@@ -70,8 +80,16 @@
 
     @Before
     public fun setup() {
+        playbackState = PlaybackState.Builder().run {
+            build()
+        }
+        mockMediaController = mock(MediaController::class.java)
+        whenever(mockMediaController.getPlaybackState()).thenReturn(playbackState)
+        mockMediaFactory = mock(MediaControllerFactory::class.java)
+        whenever(mockMediaFactory.create(any())).thenReturn(mockMediaController)
+
         fakeExecutor = FakeExecutor(FakeSystemClock())
-        keyguardMediaPlayer = KeyguardMediaPlayer(context, fakeExecutor)
+        keyguardMediaPlayer = KeyguardMediaPlayer(context, mockMediaFactory, fakeExecutor)
         mockIcon = mock(Icon::class.java)
 
         mockView = mock(View::class.java)
@@ -81,7 +99,9 @@
         whenever<TextView>(mockView.findViewById(R.id.header_artist)).thenReturn(artistView)
 
         mediaMetadata = MediaMetadata.Builder()
-        entry = NotificationEntryBuilder()
+        entry = NotificationEntryBuilder().build()
+        entry.getSbn().getNotification().extras.putParcelable(Notification.EXTRA_MEDIA_SESSION,
+                MediaSession.Token(1, null))
 
         ArchTaskExecutor.getInstance().setDelegate(taskExecutor)
 
@@ -109,7 +129,7 @@
 
     @Test
     public fun testUpdateControls() {
-        keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build())
+        keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
         FakeExecutor.exhaustExecutors(fakeExecutor)
         verify(mockView).setVisibility(View.VISIBLE)
     }
@@ -122,11 +142,22 @@
     }
 
     @Test
+    public fun testUpdateControlsNullPlaybackState() {
+        // GIVEN that the playback state is null (ie. the media session was destroyed)
+        whenever(mockMediaController.getPlaybackState()).thenReturn(null)
+        // WHEN updated
+        keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
+        FakeExecutor.exhaustExecutors(fakeExecutor)
+        // THEN the controls are cleared (ie. visibility is set to GONE)
+        verify(mockView).setVisibility(View.GONE)
+    }
+
+    @Test
     public fun testSongName() {
         val song: String = "Song"
         mediaMetadata.putText(MediaMetadata.METADATA_KEY_TITLE, song)
 
-        keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build())
+        keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
 
         assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
         assertThat(songView.getText()).isEqualTo(song)
@@ -137,7 +168,7 @@
         val artist: String = "Artist"
         mediaMetadata.putText(MediaMetadata.METADATA_KEY_ARTIST, artist)
 
-        keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build())
+        keyguardMediaPlayer.updateControls(entry, mockIcon, mediaMetadata.build())
 
         assertThat(fakeExecutor.runAllReady()).isEqualTo(1)
         assertThat(artistView.getText()).isEqualTo(artist)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index d47fcee..64590fd 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -25,6 +25,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -36,6 +37,7 @@
     @UiThreadTest
     @Test
     public void showSecurityScreen_canInflateAllModes() {
+        mDependency.injectMockDependency(KeyguardStateController.class);
         KeyguardSecurityContainer keyguardSecurityContainer =
                 new KeyguardSecurityContainer(getContext());
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 73f9d8a..7403a11 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -29,6 +29,8 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -50,6 +52,7 @@
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
+import android.media.AudioManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IRemoteCallback;
@@ -63,18 +66,24 @@
 import android.testing.TestableContext;
 import android.testing.TestableLooper;
 
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.keyguard.KeyguardUpdateMonitor.BiometricAuthenticated;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.util.RingerModeTracker;
 
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -125,6 +134,10 @@
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
     private Executor mBackgroundExecutor;
+    @Mock
+    private RingerModeTracker mRingerModeTracker;
+    @Mock
+    private LiveData<Integer> mRingerModeLiveData;
     private TestableLooper mTestableLooper;
     private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
 
@@ -156,6 +169,8 @@
         context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
         context.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
 
+        when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
+
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
         mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(context);
@@ -613,6 +628,29 @@
         assertThat(mKeyguardUpdateMonitor.getSecondaryLockscreenRequirement(user)).isNull();
     }
 
+    @Test
+    public void testRingerModeChange() {
+        ArgumentCaptor<Observer<Integer>> captor = ArgumentCaptor.forClass(Observer.class);
+        verify(mRingerModeLiveData).observeForever(captor.capture());
+        Observer<Integer> observer = captor.getValue();
+
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        observer.onChanged(AudioManager.RINGER_MODE_NORMAL);
+        observer.onChanged(AudioManager.RINGER_MODE_SILENT);
+        observer.onChanged(AudioManager.RINGER_MODE_VIBRATE);
+
+        mTestableLooper.processAllMessages();
+
+        InOrder orderVerify = inOrder(callback);
+        orderVerify.verify(callback).onRingerModeChanged(anyInt()); // Initial update on register
+        orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_NORMAL);
+        orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_SILENT);
+        orderVerify.verify(callback).onRingerModeChanged(AudioManager.RINGER_MODE_VIBRATE);
+    }
+
     private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver) {
         BroadcastReceiver.PendingResult pendingResult =
                 new BroadcastReceiver.PendingResult(Activity.RESULT_OK,
@@ -643,7 +681,8 @@
         protected TestableKeyguardUpdateMonitor(Context context) {
             super(context,
                     TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
-                    mBroadcastDispatcher, mDumpManager, mBackgroundExecutor);
+                    mBroadcastDispatcher, mDumpManager,
+                    mRingerModeTracker, mBackgroundExecutor);
             mStrongAuthTracker = KeyguardUpdateMonitorTest.this.mStrongAuthTracker;
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 69e933e..45f9437 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -52,6 +52,7 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import junit.framework.Assert;
 
@@ -69,6 +70,7 @@
     private ForegroundServiceController mFsc;
     private ForegroundServiceNotificationListener mListener;
     private NotificationEntryListener mEntryListener;
+    private final FakeSystemClock mClock = new FakeSystemClock();
     @Mock private NotificationEntryManager mEntryManager;
     @Mock private AppOpsController mAppOpsController;
     @Mock private Handler mMainHandler;
@@ -80,9 +82,10 @@
         allowTestableLooperAsMainThread();
 
         MockitoAnnotations.initMocks(this);
-        mFsc = new ForegroundServiceController(mEntryManager, mAppOpsController, mMainHandler);
+        mFsc = new ForegroundServiceController(
+                mEntryManager, mAppOpsController, mMainHandler);
         mListener = new ForegroundServiceNotificationListener(
-                mContext, mFsc, mEntryManager, mNotifPipeline);
+                mContext, mFsc, mEntryManager, mNotifPipeline, mClock);
         ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
                 ArgumentCaptor.forClass(NotificationEntryListener.class);
         verify(mEntryManager).addNotificationEntryListener(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
index 46a473b..bca8dee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceNotificationListenerTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui;
 
 import static com.android.systemui.ForegroundServiceLifetimeExtender.MIN_FGS_TIME_MS;
-import static com.android.systemui.statusbar.NotificationEntryHelper.modifySbn;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -29,6 +28,7 @@
 
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -37,12 +37,15 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class ForegroundServiceNotificationListenerTest extends SysuiTestCase {
-    private ForegroundServiceLifetimeExtender mExtender = new ForegroundServiceLifetimeExtender();
+    private ForegroundServiceLifetimeExtender mExtender;
     private NotificationEntry mEntry;
     private Notification mNotif;
+    private final FakeSystemClock mClock = new FakeSystemClock();
 
     @Before
     public void setup() {
+        mExtender = new ForegroundServiceLifetimeExtender(mClock);
+
         mNotif = new Notification.Builder(mContext, "")
                 .setSmallIcon(R.drawable.ic_person)
                 .setContentTitle("Title")
@@ -50,6 +53,7 @@
                 .build();
 
         mEntry = new NotificationEntryBuilder()
+                .setCreationTime(mClock.uptimeMillis())
                 .setNotification(mNotif)
                 .build();
     }
@@ -62,27 +66,26 @@
         // Extend the lifetime of a FGS notification iff it has not been visible
         // for the minimum time
         mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        modifySbn(mEntry)
-                .setPostTime(System.currentTimeMillis())
-                .build();
+
+        // No time has elapsed, keep showing
         assertTrue(mExtender.shouldExtendLifetime(mEntry));
     }
 
     @Test
     public void testShouldExtendLifetime_shouldNot_foreground() {
         mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        modifySbn(mEntry)
-                .setPostTime(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1)
-                .build();
+
+        // Entry was created at mClock.uptimeMillis(), advance it MIN_FGS_TIME_MS + 1
+        mClock.advanceTime(MIN_FGS_TIME_MS + 1);
         assertFalse(mExtender.shouldExtendLifetime(mEntry));
     }
 
     @Test
     public void testShouldExtendLifetime_shouldNot_notForeground() {
         mNotif.flags = 0;
-        modifySbn(mEntry)
-                .setPostTime(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1)
-                .build();
+
+        // Entry was created at mClock.uptimeMillis(), advance it MIN_FGS_TIME_MS + 1
+        mClock.advanceTime(MIN_FGS_TIME_MS + 1);
         assertFalse(mExtender.shouldExtendLifetime(mEntry));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
index 475023e..5227aaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ImageWallpaperTest.java
@@ -160,7 +160,7 @@
                 LOW_BMP_HEIGHT /* bmpHeight */,
                 LOW_BMP_WIDTH /* surfaceWidth */,
                 LOW_BMP_HEIGHT /* surfaceHeight */,
-                true /* assertion */);
+                false /* assertion */);
     }
 
     @Test
@@ -172,7 +172,7 @@
                 INVALID_BMP_HEIGHT /* bmpHeight */,
                 ImageWallpaper.GLEngine.MIN_SURFACE_WIDTH /* surfaceWidth */,
                 ImageWallpaper.GLEngine.MIN_SURFACE_HEIGHT /* surfaceHeight */,
-                true /* assertion */);
+                false /* assertion */);
     }
 
     private void verifySurfaceSizeAndAssertTransition(int bmpWidth, int bmpHeight,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 32604f8..b2c3586 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -38,6 +38,8 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -97,6 +99,12 @@
         // A lot of tests get the LocalBluetoothManager, often via several layers of indirection.
         // None of them actually need it.
         mDependency.injectMockDependency(LocalBluetoothManager.class);
+
+        // Notifications tests are injecting one of these, causing many classes (including
+        // KeyguardUpdateMonitor to be created (injected).
+        // TODO(b/1531701009) Clean up NotificationContentView creation to prevent this
+        mDependency.injectMockDependency(SmartReplyController.class);
+        mDependency.injectMockDependency(NotificationBlockingHelperManager.class);
     }
 
     @After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 037f04ec..e472de3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -374,7 +374,7 @@
         assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey()));
         assertTrue(mBubbleController.hasBubbles());
 
-        mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
         assertFalse(mNotificationShadeWindowController.getBubblesShowing());
         verify(mNotificationEntryManager, times(3)).updateNotifications(any());
         assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
@@ -399,7 +399,7 @@
 
         // Expand the stack
         BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
         assertTrue(mNotificationShadeWindowController.getBubbleExpanded());
@@ -436,7 +436,7 @@
 
         // Expand
         BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
 
@@ -448,7 +448,7 @@
                 mRow2.getEntry()));
 
         // Switch which bubble is expanded
-        mBubbleController.selectBubble(mRow.getEntry().getKey());
+        mBubbleData.setSelectedBubble(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
         mBubbleData.setExpanded(true);
         assertEquals(mRow.getEntry(),
                 mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry());
@@ -482,7 +482,7 @@
         assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
 
         // Expand
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
 
@@ -510,7 +510,7 @@
         assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
 
         // Expand
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
 
@@ -544,7 +544,7 @@
 
         // Expand
         BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
 
         assertTrue(mSysUiStateBubblesExpanded);
 
@@ -726,7 +726,7 @@
     public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.updateBubble(mRow2.getEntry());
-        mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
         verify(mDeleteIntent, times(2)).send();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 545de21..5f4f2ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -321,7 +321,7 @@
         assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().getKey()));
         assertTrue(mBubbleController.hasBubbles());
 
-        mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
         assertFalse(mNotificationShadeWindowController.getBubblesShowing());
         verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
         assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
@@ -344,7 +344,7 @@
 
         // Expand the stack
         BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
         assertTrue(mNotificationShadeWindowController.getBubbleExpanded());
@@ -376,7 +376,7 @@
 
         // Expand
         BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
 
@@ -385,7 +385,7 @@
         assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry()));
 
         // Switch which bubble is expanded
-        mBubbleController.selectBubble(mRow.getEntry().getKey());
+        mBubbleData.setSelectedBubble(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()));
         mBubbleData.setExpanded(true);
         assertEquals(mRow.getEntry(),
                 mBubbleData.getBubbleWithKey(stackView.getExpandedBubble().getKey()).getEntry());
@@ -416,7 +416,7 @@
         assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
 
         // Expand
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
 
@@ -442,7 +442,7 @@
         assertTrue(mBubbleData.getBubbleWithKey(mRow.getEntry().getKey()).showDot());
 
         // Expand
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
 
@@ -474,7 +474,7 @@
 
         // Expand
         BubbleStackView stackView = mBubbleController.getStackView();
-        mBubbleController.expandStack();
+        mBubbleData.setExpanded(true);
 
         assertTrue(mBubbleController.isStackExpanded());
         verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
@@ -628,7 +628,7 @@
     public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
         mBubbleController.updateBubble(mRow.getEntry());
         mBubbleController.updateBubble(mRow2.getEntry());
-        mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+        mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
         verify(mDeleteIntent, times(2)).send();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
index 88316f2..bb003ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -125,7 +125,7 @@
         loadSubscriberCaptor.value.onSubscribe(Binder(), subscription)
 
         canceller.run()
-        verify(subscription).cancel()
+        verify(providers[0]).cancelSubscription(subscription)
     }
 
     @Test
@@ -145,7 +145,7 @@
 
         loadSubscriberCaptor.value.onComplete(b)
         canceller.run()
-        verify(subscription, never()).cancel()
+        verify(providers[0], never()).cancelSubscription(subscription)
     }
 
     @Test
@@ -203,7 +203,7 @@
 
         loadSubscriberCaptor.value.onError(b, "")
         canceller.run()
-        verify(subscription, never()).cancel()
+        verify(providers[0], never()).cancelSubscription(subscription)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
index c9bb401..9985d21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
@@ -20,8 +20,10 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.testing.AndroidTestingRunner;
@@ -56,6 +58,7 @@
         mDockManagerFake = spy(new DockManagerFake());
         mDockHandler = new DozeDockHandler(mConfig, mMachine, mDockManagerFake);
 
+        when(mMachine.getState()).thenReturn(State.DOZE_AOD);
         doReturn(true).when(mConfig).alwaysOnEnabled(anyInt());
         mDockHandler.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
     }
@@ -101,4 +104,31 @@
 
         verify(mMachine).requestState(eq(State.DOZE));
     }
+
+    @Test
+    public void onEvent_dockedWhilePulsing_wontRequestStateChange() {
+        when(mMachine.getState()).thenReturn(State.DOZE_PULSING);
+
+        mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED);
+
+        verify(mMachine, never()).requestState(any(State.class));
+    }
+
+    @Test
+    public void onEvent_noneWhilePulsing_wontRequestStateChange() {
+        when(mMachine.getState()).thenReturn(State.DOZE_PULSING);
+
+        mDockManagerFake.setDockEvent(DockManager.STATE_NONE);
+
+        verify(mMachine, never()).requestState(any(State.class));
+    }
+
+    @Test
+    public void onEvent_hideWhilePulsing_wontRequestStateChange() {
+        when(mMachine.getState()).thenReturn(State.DOZE_PULSING);
+
+        mDockManagerFake.setDockEvent(DockManager.STATE_DOCKED_HIDE);
+
+        verify(mMachine, never()).requestState(any(State.class));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index c483314..1f07f46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -254,6 +254,17 @@
     }
 
     @Test
+    public void testPulseDone_whileDockedAoD_staysDockedAod() {
+        when(mDockManager.isDocked()).thenReturn(true);
+        mMachine.requestState(INITIALIZED);
+        mMachine.requestState(DOZE_AOD_DOCKED);
+
+        mMachine.requestState(DOZE_PULSE_DONE);
+
+        verify(mPartMock, never()).transitionTo(DOZE_AOD_DOCKED, DOZE_PULSE_DONE);
+    }
+
+    @Test
     public void testPulseDone_dozeSuppressed_afterDocked_goesToDoze() {
         when(mHost.isDozeSuppressed()).thenReturn(true);
         when(mDockManager.isDocked()).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 137a126..300cb21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -18,6 +18,7 @@
 
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManager;
@@ -52,6 +53,8 @@
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.RingerModeLiveData;
+import com.android.systemui.util.RingerModeTracker;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -95,6 +98,8 @@
     @Mock private ControlsListingController mControlsListingController;
     @Mock private ControlsController mControlsController;
     @Mock private UiEventLogger mUiEventLogger;
+    @Mock private RingerModeTracker mRingerModeTracker;
+    @Mock private RingerModeLiveData mRingerModeLiveData;
 
     private TestableLooper mTestableLooper;
 
@@ -103,6 +108,8 @@
         MockitoAnnotations.initMocks(this);
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
+
+        when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
         mGlobalActionsDialog = new GlobalActionsDialog(mContext,
                 mWindowManagerFuncs,
                 mAudioManager,
@@ -133,7 +140,8 @@
                 mBackgroundExecutor,
                 mControlsListingController,
                 mControlsController,
-                mUiEventLogger
+                mUiEventLogger,
+                mRingerModeTracker
         );
     }
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
index 260f520..d407b8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarObserverTest.kt
@@ -67,10 +67,11 @@
         val isEnabled = false
         val data = SeekBarViewModel.Progress(isEnabled, false, null, null, null)
         observer.onChanged(data)
-        // THEN seek bar visibility is set to GONE
-        assertThat(seekBarView.getVisibility()).isEqualTo(View.GONE)
-        assertThat(elapsedTimeView.getVisibility()).isEqualTo(View.GONE)
-        assertThat(totalTimeView.getVisibility()).isEqualTo(View.GONE)
+        // THEN seek bar shows just a line with no text
+        assertThat(seekBarView.isEnabled()).isFalse()
+        assertThat(seekBarView.getThumb().getAlpha()).isEqualTo(0)
+        assertThat(elapsedTimeView.getText()).isEqualTo("")
+        assertThat(totalTimeView.getText()).isEqualTo("")
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
index 0d66340..56a7484 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
@@ -51,7 +51,6 @@
 
     private PipAnimationController mPipAnimationController;
 
-    @Mock
     private SurfaceControl mLeash;
 
     @Mock
@@ -61,6 +60,10 @@
     public void setUp() throws Exception {
         mPipAnimationController = new PipAnimationController(
                 mContext, new PipSurfaceTransactionHelper(mContext));
+        mLeash = new SurfaceControl.Builder()
+                .setContainerLayer()
+                .setName("FakeLeash")
+                .build();
         MockitoAnnotations.initMocks(this);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
index 0bf0f04..425bf88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
@@ -17,6 +17,7 @@
 package com.android.systemui.pip;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import android.content.ComponentName;
@@ -56,11 +57,15 @@
 
     private PipBoundsHandler mPipBoundsHandler;
     private DisplayInfo mDefaultDisplayInfo;
+    private ComponentName mTestComponentName1;
+    private ComponentName mTestComponentName2;
 
     @Before
     public void setUp() throws Exception {
         initializeMockResources();
         mPipBoundsHandler = new PipBoundsHandler(mContext, new PipSnapAlgorithm(mContext));
+        mTestComponentName1 = new ComponentName(mContext, "component1");
+        mTestComponentName2 = new ComponentName(mContext, "component2");
 
         mPipBoundsHandler.onDisplayInfoChanged(mDefaultDisplayInfo);
     }
@@ -121,7 +126,7 @@
         };
         for (float aspectRatio : aspectRatios) {
             final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                    aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+                    mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
             final float actualAspectRatio =
                     destinationBounds.width() / (destinationBounds.height() * 1f);
             assertEquals("Destination bounds matches the given aspect ratio",
@@ -137,7 +142,7 @@
         };
         for (float aspectRatio : invalidAspectRatios) {
             final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                    aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+                    mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
             final float actualAspectRatio =
                     destinationBounds.width() / (destinationBounds.height() * 1f);
             assertEquals("Destination bounds fallbacks to default aspect ratio",
@@ -153,7 +158,7 @@
         currentBounds.right = (int) (currentBounds.height() * aspectRatio) + currentBounds.left;
 
         final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                aspectRatio, currentBounds, EMPTY_MINIMAL_SIZE);
+                mTestComponentName1, aspectRatio, currentBounds, EMPTY_MINIMAL_SIZE);
 
         final float actualAspectRatio =
                 destinationBounds.width() / (destinationBounds.height() * 1f);
@@ -177,7 +182,7 @@
             final float aspectRatio = aspectRatios[i];
             final Size minimalSize = minimalSizes[i];
             final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                    aspectRatio, EMPTY_CURRENT_BOUNDS, minimalSize);
+                    mTestComponentName1, aspectRatio, EMPTY_CURRENT_BOUNDS, minimalSize);
             assertTrue("Destination bounds is no smaller than minimal requirement",
                     (destinationBounds.width() == minimalSize.getWidth()
                             && destinationBounds.height() >= minimalSize.getHeight())
@@ -198,7 +203,7 @@
         final Size minSize = new Size(currentBounds.width() / 2, currentBounds.height() / 2);
 
         final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
-                aspectRatio, currentBounds, minSize);
+                mTestComponentName1, aspectRatio, currentBounds, minSize);
 
         assertTrue("Destination bounds ignores minimal size",
                 destinationBounds.width() > minSize.getWidth()
@@ -206,81 +211,92 @@
     }
 
     @Test
+    public void getDestinationBounds_withDifferentComponentName_ignoreLastPosition() {
+        final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+
+        oldPosition.offset(0, -100);
+        mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldPosition);
+
+        final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName2,
+                DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
+
+        assertNonBoundsInclusionWithMargin("ignore saved bounds", oldPosition, newPosition);
+    }
+
+    @Test
     public void setShelfHeight_offsetBounds() {
         final int shelfHeight = 100;
-        final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
+        final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
                 DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         mPipBoundsHandler.setShelfHeight(true, shelfHeight);
-        final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
+        final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
                 DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         oldPosition.offset(0, -shelfHeight);
-        assertBoundsWithMargin("offsetBounds by shelf", oldPosition, newPosition);
+        assertBoundsInclusionWithMargin("offsetBounds by shelf", oldPosition, newPosition);
     }
 
     @Test
     public void onImeVisibilityChanged_offsetBounds() {
         final int imeHeight = 100;
-        final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
+        final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
                 DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         mPipBoundsHandler.onImeVisibilityChanged(true, imeHeight);
-        final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
+        final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
                 DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         oldPosition.offset(0, -imeHeight);
-        assertBoundsWithMargin("offsetBounds by IME", oldPosition, newPosition);
+        assertBoundsInclusionWithMargin("offsetBounds by IME", oldPosition, newPosition);
     }
 
     @Test
     public void onSaveReentryBounds_restoreLastPosition() {
-        final ComponentName componentName = new ComponentName(mContext, "component1");
-        final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(
+        final Rect oldPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
                 DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
         oldPosition.offset(0, -100);
-        mPipBoundsHandler.onSaveReentryBounds(componentName, oldPosition);
+        mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, oldPosition);
 
-        final Rect newPosition = mPipBoundsHandler.getDestinationBounds(
+        final Rect newPosition = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
                 DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
-        assertBoundsWithMargin("restoreLastPosition", oldPosition, newPosition);
+        assertBoundsInclusionWithMargin("restoreLastPosition", oldPosition, newPosition);
     }
 
     @Test
     public void onResetReentryBounds_useDefaultBounds() {
-        final ComponentName componentName = new ComponentName(mContext, "component1");
-        final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(
+        final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
                 DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
         final Rect newBounds = new Rect(defaultBounds);
         newBounds.offset(0, -100);
-        mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds);
+        mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, newBounds);
 
-        mPipBoundsHandler.onResetReentryBounds(componentName);
-        final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(
+        mPipBoundsHandler.onResetReentryBounds(mTestComponentName1);
+        final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
                 DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
-        assertBoundsWithMargin("useDefaultBounds", defaultBounds, actualBounds);
+        assertBoundsInclusionWithMargin("useDefaultBounds", defaultBounds, actualBounds);
     }
 
     @Test
     public void onResetReentryBounds_componentMismatch_restoreLastPosition() {
-        final ComponentName componentName = new ComponentName(mContext, "component1");
-        final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(
+        final Rect defaultBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
                 DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
         final Rect newBounds = new Rect(defaultBounds);
         newBounds.offset(0, -100);
-        mPipBoundsHandler.onSaveReentryBounds(componentName, newBounds);
+        mPipBoundsHandler.onSaveReentryBounds(mTestComponentName1, newBounds);
 
-        mPipBoundsHandler.onResetReentryBounds(new ComponentName(mContext, "component2"));
-        final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(
+        mPipBoundsHandler.onResetReentryBounds(mTestComponentName2);
+        final Rect actualBounds = mPipBoundsHandler.getDestinationBounds(mTestComponentName1,
                 DEFAULT_ASPECT_RATIO, EMPTY_CURRENT_BOUNDS, EMPTY_MINIMAL_SIZE);
 
-        assertBoundsWithMargin("restoreLastPosition", newBounds, actualBounds);
+        assertBoundsInclusionWithMargin("restoreLastPosition", newBounds, actualBounds);
     }
 
-    private void assertBoundsWithMargin(String from, Rect expected, Rect actual) {
+    private void assertBoundsInclusionWithMargin(String from, Rect expected, Rect actual) {
         final Rect expectedWithMargin = new Rect(expected);
         expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN);
         assertTrue(from + ": expect " + expected
@@ -288,4 +304,13 @@
                 + " with error margin " + ROUNDING_ERROR_MARGIN,
                 expectedWithMargin.contains(actual));
     }
+
+    private void assertNonBoundsInclusionWithMargin(String from, Rect expected, Rect actual) {
+        final Rect expectedWithMargin = new Rect(expected);
+        expectedWithMargin.inset(-ROUNDING_ERROR_MARGIN, -ROUNDING_ERROR_MARGIN);
+        assertFalse(from + ": expect " + expected
+                        + " not contains " + actual
+                        + " with error margin " + ROUNDING_ERROR_MARGIN,
+                expectedWithMargin.contains(actual));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
index 43cf83f..81f9546 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java
@@ -29,6 +29,7 @@
 import com.android.internal.logging.InstanceId;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import java.util.ArrayList;
 
@@ -44,16 +45,22 @@
 public class NotificationEntryBuilder {
     private final SbnBuilder mSbnBuilder = new SbnBuilder();
     private final RankingBuilder mRankingBuilder = new RankingBuilder();
+    private final FakeSystemClock mClock = new FakeSystemClock();
     private StatusBarNotification mSbn = null;
 
     /* ListEntry properties */
     private GroupEntry mParent;
     private int mSection = -1;
 
+    /* If set, use this creation time instead of mClock.uptimeMillis */
+    private long mCreationTime = -1;
+
     public NotificationEntry build() {
         StatusBarNotification sbn = mSbn != null ? mSbn : mSbnBuilder.build();
         mRankingBuilder.setKey(sbn.getKey());
-        final NotificationEntry entry = new NotificationEntry(sbn, mRankingBuilder.build());
+        long creationTime = mCreationTime != -1 ? mCreationTime : mClock.uptimeMillis();
+        final NotificationEntry entry = new NotificationEntry(
+                sbn, mRankingBuilder.build(), mClock.uptimeMillis());
 
         /* ListEntry properties */
         entry.setParent(mParent);
@@ -86,6 +93,14 @@
         return this;
     }
 
+    /**
+     * Set the creation time
+     */
+    public NotificationEntryBuilder setCreationTime(long creationTime) {
+        mCreationTime = creationTime;
+        return this;
+    }
+
     /* Delegated to SbnBuilder */
 
     public NotificationEntryBuilder setPkg(String pkg) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 5b0b668..1a022ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -52,6 +52,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -71,6 +72,7 @@
     private int mId;
 
     private NotificationEntry mEntry;
+    private final FakeSystemClock mClock = new FakeSystemClock();
 
     @Before
     public void setup() {
@@ -187,7 +189,7 @@
                 .build();
 
         NotificationEntry entry =
-                new NotificationEntry(sbn, ranking);
+                new NotificationEntry(sbn, ranking, mClock.uptimeMillis());
 
         assertEquals(systemGeneratedSmartActions, entry.getSmartActions());
         assertEquals(NOTIFICATION_CHANNEL, entry.getChannel());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index be026f7..1c6e5a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -18,6 +18,8 @@
 
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
+
 import static junit.framework.Assert.assertNotNull;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -139,6 +141,7 @@
     private NotificationRowBinderImpl mRowBinder;
     private Handler mHandler;
     private FakeExecutor mBgExecutor;
+    private RowContentBindStage mRowContentBindStage;
 
     @Before
     public void setUp() {
@@ -147,10 +150,13 @@
 
         mHandler = Handler.createAsync(TestableLooper.get(this).getLooper());
 
+        // Add an action so heads up content views are made
+        Notification.Action action = new Notification.Action.Builder(null, null, null).build();
         Notification notification = new Notification.Builder(mContext)
                 .setSmallIcon(R.drawable.ic_person)
                 .setContentTitle(TEST_TITLE)
                 .setContentText(TEST_TEXT)
+                .setActions(action)
                 .build();
         mSbn = new SbnBuilder()
                 .setNotification(notification)
@@ -192,11 +198,11 @@
                 () -> mock(SmartReplyController.class),
                 mock(ConversationNotificationProcessor.class),
                 mBgExecutor);
-        RowContentBindStage stage = new RowContentBindStage(
+        mRowContentBindStage = new RowContentBindStage(
                 binder,
                 mock(NotifInflationErrorManager.class),
                 mock(RowContentBindStageLogger.class));
-        pipeline.setStage(stage);
+        pipeline.setStage(mRowContentBindStage);
 
         ArgumentCaptor<ExpandableNotificationRow> viewCaptor =
                 ArgumentCaptor.forClass(ExpandableNotificationRow.class);
@@ -232,7 +238,7 @@
                                 "FOOBAR", "FOOBAR",
                                 mKeyguardBypassController,
                                 mGroupManager,
-                                stage,
+                                mRowContentBindStage,
                                 mock(NotificationLogger.class),
                                 mHeadsUpManager,
                                 mPresenter,
@@ -256,7 +262,7 @@
                 mRemoteInputManager,
                 mLockscreenUserManager,
                 pipeline,
-                stage,
+                mRowContentBindStage,
                 mNotificationInterruptionStateProvider,
                 RowInflaterTask::new,
                 mExpandableNotificationRowComponentBuilder,
@@ -365,6 +371,27 @@
         verify(mPresenter).updateNotificationViews();
     }
 
+    @Test
+    public void testContentViewInflationDuringRowInflationInflatesCorrectViews() {
+        // GIVEN a notification is added and the row is inflating
+        mEntryManager.addNotification(mSbn, mRankingMap);
+        ArgumentCaptor<NotificationEntry> entryCaptor = ArgumentCaptor.forClass(
+                NotificationEntry.class);
+        verify(mEntryListener).onPendingEntryAdded(entryCaptor.capture());
+        NotificationEntry entry = entryCaptor.getValue();
+
+        // WHEN we try to bind a content view
+        mRowContentBindStage.getStageParams(entry).requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
+        mRowContentBindStage.requestRebind(entry, null);
+
+        waitForInflation();
+
+        // THEN the notification has its row and all relevant content views inflated
+        assertNotNull(entry.getRow());
+        assertNotNull(entry.getRow().getPrivateLayout().getContractedChild());
+        assertNotNull(entry.getRow().getPrivateLayout().getHeadsUpChild());
+    }
+
     /**
      * Wait for inflation to finish.
      *
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
new file mode 100644
index 0000000..2489c30
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.content.BroadcastReceiver
+import android.content.IntentFilter
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.lifecycle.Observer
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import org.junit.After
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class RingerModeLiveDataTest : SysuiTestCase() {
+
+    companion object {
+        private fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+        private fun <T> any(): T = Mockito.any()
+        private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+        private val INTENT = "INTENT"
+    }
+
+    @Mock
+    private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock
+    private lateinit var valueSupplier: () -> Int
+    @Mock
+    private lateinit var observer: Observer<Int>
+    @Captor
+    private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
+    @Captor
+    private lateinit var intentFilterCaptor: ArgumentCaptor<IntentFilter>
+
+    // Run everything immediately
+    private val executor = Executor { it.run() }
+    private lateinit var liveData: RingerModeLiveData
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        liveData = RingerModeLiveData(broadcastDispatcher, executor, INTENT, valueSupplier)
+    }
+
+    @After
+    fun tearDown() {
+        liveData.removeObserver(observer)
+    }
+
+    @Test
+    fun testInit_broadcastNotRegistered() {
+        verifyNoMoreInteractions(broadcastDispatcher)
+    }
+
+    @Test
+    fun testOnActive_broadcastRegistered() {
+        liveData.observeForever(observer)
+        verify(broadcastDispatcher).registerReceiver(any(), any(), eq(executor), eq(UserHandle.ALL))
+    }
+
+    @Test
+    fun testOnActive_intentFilterHasIntent() {
+        liveData.observeForever(observer)
+        verify(broadcastDispatcher).registerReceiver(any(), capture(intentFilterCaptor), any(),
+                any())
+        assertTrue(intentFilterCaptor.value.hasAction(INTENT))
+    }
+
+    @Test
+    fun testOnActive_valueObtained() {
+        liveData.observeForever(observer)
+        verify(valueSupplier).invoke()
+    }
+
+    @Test
+    fun testOnInactive_broadcastUnregistered() {
+        liveData.observeForever(observer)
+        liveData.removeObserver(observer)
+        verify(broadcastDispatcher).unregisterReceiver(any())
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt
index f1672b1..f6b7b74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/magnetictarget/MagnetizedObjectTest.kt
@@ -106,6 +106,10 @@
                 location[1] = targetCenterY - targetSize / 2 // y = 800
             }
         }.`when`(targetView).getLocationOnScreen(ArgumentMatchers.any())
+        doAnswer { invocation ->
+            (invocation.arguments[0] as Runnable).run()
+            true
+        }.`when`(targetView).post(ArgumentMatchers.any())
         `when`(targetView.context).thenReturn(context)
 
         magneticTarget = MagnetizedObject.MagneticTarget(targetView, magneticFieldRadius)
@@ -408,6 +412,10 @@
         `when`(secondTargetView.width).thenReturn(targetSize)  // width = 200
         `when`(secondTargetView.height).thenReturn(targetSize) // height = 200
         doAnswer { invocation ->
+            (invocation.arguments[0] as Runnable).run()
+            true
+        }.`when`(secondTargetView).post(ArgumentMatchers.any())
+        doAnswer { invocation ->
             (invocation.arguments[0] as IntArray).also { location ->
                 // Return the top left of the target.
                 location[0] = secondTargetCenterX - targetSize / 2 // x = 0
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 8cc83dd..6166cd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -31,6 +31,7 @@
 import android.os.Handler;
 import android.os.Process;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 
@@ -38,7 +39,10 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.util.RingerModeLiveData;
+import com.android.systemui.util.RingerModeTracker;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,6 +53,7 @@
 
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
+@TestableLooper.RunWithLooper
 public class VolumeDialogControllerImplTest extends SysuiTestCase {
 
     TestableVolumeDialogControllerImpl mVolumeController;
@@ -56,18 +61,35 @@
     StatusBar mStatusBar;
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
+    @Mock
+    private RingerModeTracker mRingerModeTracker;
+    @Mock
+    private RingerModeLiveData mRingerModeLiveData;
+    @Mock
+    private RingerModeLiveData mRingerModeInternalLiveData;
 
     @Before
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
+        when(mRingerModeTracker.getRingerModeInternal()).thenReturn(mRingerModeInternalLiveData);
+        // Initial non-set value
+        when(mRingerModeLiveData.getValue()).thenReturn(-1);
+        when(mRingerModeInternalLiveData.getValue()).thenReturn(-1);
+
         mCallback = mock(VolumeDialogControllerImpl.C.class);
         mStatusBar = mock(StatusBar.class);
         mVolumeController = new TestableVolumeDialogControllerImpl(mContext, mCallback, mStatusBar,
-                mBroadcastDispatcher);
+                mBroadcastDispatcher, mRingerModeTracker);
         mVolumeController.setEnableDialogs(true, true);
     }
 
+    @After
+    public void tearDown() {
+        mVolumeController.destroy();
+    }
+
     @Test
     public void testRegisteredWithDispatcher() {
         verify(mBroadcastDispatcher).registerReceiverWithHandler(any(BroadcastReceiver.class),
@@ -109,7 +131,7 @@
         TestableVolumeDialogControllerImpl
                 nullStatusBarTestableDialog =
                 new TestableVolumeDialogControllerImpl(
-                        mContext, callback, null, mBroadcastDispatcher);
+                        mContext, callback, null, mBroadcastDispatcher, mRingerModeTracker);
         nullStatusBarTestableDialog.setEnableDialogs(true, true);
         nullStatusBarTestableDialog.onVolumeChangedW(0, AudioManager.FLAG_SHOW_UI);
         verify(callback, times(1)).onShowRequested(Events.SHOW_REASON_VOLUME_CHANGED);
@@ -127,12 +149,26 @@
         mVolumeController.mMediaSessionsCallbacksW.onRemoteRemoved(token);
     }
 
+    @Test
+    public void testRingerModeLiveDataObserving() {
+        verify(mRingerModeLiveData).observeForever(any());
+        verify(mRingerModeInternalLiveData).observeForever(any());
+    }
+
+    @Test
+    public void testRingerModeOnDestroy_observersRemoved() {
+        mVolumeController.destroy();
+
+        verify(mRingerModeLiveData).removeObserver(any());
+        verify(mRingerModeInternalLiveData).removeObserver(any());
+    }
+
     static class TestableVolumeDialogControllerImpl extends VolumeDialogControllerImpl {
         TestableVolumeDialogControllerImpl(Context context, C callback, StatusBar s,
-                BroadcastDispatcher broadcastDispatcher) {
+                BroadcastDispatcher broadcastDispatcher, RingerModeTracker ringerModeTracker) {
             super(
                     context, broadcastDispatcher,
-                    s == null ? Optional.empty() : Optional.of(() -> s));
+                    s == null ? Optional.empty() : Optional.of(() -> s), ringerModeTracker);
             mCallbacks = callback;
         }
     }
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 190b443..5b052df 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -25,7 +25,7 @@
     ],
     static_libs: [
         "androidx.annotation_annotation",
-        "netd_aidl_interface-unstable-java",
+        "netd_aidl_interface-V3-java",
         "netlink-client",
         "networkstack-aidl-interfaces-unstable-java",
         "android.hardware.tetheroffload.config-V1.0-java",
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 3509801..cc095a0 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -571,9 +571,8 @@
             /**
              * Configure tethering with static IPv4 assignment.
              *
-             * The clientAddress must be in the localIPv4Address prefix. A DHCP server will be
-             * started, but will only be able to offer the client address. The two addresses must
-             * be in the same prefix.
+             * A DHCP server will be started, but will only be able to offer the client address.
+             * The two addresses must be in the same prefix.
              *
              * @param localIPv4Address The preferred local IPv4 link address to use.
              * @param clientAddress The static client address.
@@ -584,10 +583,7 @@
                     @NonNull final LinkAddress clientAddress) {
                 Objects.requireNonNull(localIPv4Address);
                 Objects.requireNonNull(clientAddress);
-                if (localIPv4Address.getPrefixLength() != clientAddress.getPrefixLength()
-                        || !localIPv4Address.isIpv4() || !clientAddress.isIpv4()
-                        || !new IpPrefix(localIPv4Address.toString()).equals(
-                        new IpPrefix(clientAddress.toString()))) {
+                if (!checkStaticAddressConfiguration(localIPv4Address, clientAddress)) {
                     throw new IllegalArgumentException("Invalid server or client addresses");
                 }
 
@@ -657,6 +653,19 @@
         }
 
         /**
+         * Check whether the two addresses are ipv4 and in the same prefix.
+         * @hide
+         */
+        public static boolean checkStaticAddressConfiguration(
+                @NonNull final LinkAddress localIPv4Address,
+                @NonNull final LinkAddress clientAddress) {
+            return localIPv4Address.getPrefixLength() == clientAddress.getPrefixLength()
+                    && localIPv4Address.isIpv4() && clientAddress.isIpv4()
+                    && new IpPrefix(localIPv4Address.toString()).equals(
+                    new IpPrefix(clientAddress.toString()));
+        }
+
+        /**
          * Get a TetheringRequestParcel from the configuration
          * @hide
          */
diff --git a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
index d6bc063..82a26be 100644
--- a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
+++ b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -18,10 +18,12 @@
 
 import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
 
-import android.annotation.NonNull;
 import android.net.LinkAddress;
 import android.util.ArraySet;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.net.Inet4Address;
 import java.util.Collection;
 import java.util.Collections;
@@ -160,6 +162,17 @@
         return this;
     }
 
+    /**
+     * Set the client address to tell DHCP server only offer this address.
+     * The client's prefix length is the same as server's.
+     *
+     * <p>If not set, the default value is null.
+     */
+    public DhcpServingParamsParcelExt setSingleClientAddr(@Nullable Inet4Address clientAddr) {
+        this.clientAddr = clientAddr == null ? 0 : inet4AddressToIntHTH(clientAddr);
+        return this;
+    }
+
     private static int[] toIntArray(@NonNull Collection<Inet4Address> addrs) {
         int[] res = new int[addrs.size()];
         int i = 0;
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 5b6fe91..1dac5b7 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -18,6 +18,7 @@
 
 import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.TetheringManager.TetheringRequest.checkStaticAddressConfiguration;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 import static android.net.util.NetworkConstants.FF;
@@ -511,17 +512,24 @@
         }
     }
 
-    private boolean startDhcp(Inet4Address addr, int prefixLen) {
+    private boolean startDhcp(final LinkAddress serverLinkAddr, final LinkAddress clientLinkAddr) {
         if (mUsingLegacyDhcp) {
             return true;
         }
+
+        final Inet4Address addr = (Inet4Address) serverLinkAddr.getAddress();
+        final int prefixLen = serverLinkAddr.getPrefixLength();
+        final Inet4Address clientAddr = clientLinkAddr == null ? null :
+                (Inet4Address) clientLinkAddr.getAddress();
+
         final DhcpServingParamsParcel params;
         params = new DhcpServingParamsParcelExt()
                 .setDefaultRouters(addr)
                 .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
                 .setDnsServers(addr)
-                .setServerAddr(new LinkAddress(addr, prefixLen))
-                .setMetered(true);
+                .setServerAddr(serverLinkAddr)
+                .setMetered(true)
+                .setSingleClientAddr(clientAddr);
         // TODO: also advertise link MTU
 
         mDhcpServerStartIndex++;
@@ -556,9 +564,10 @@
         }
     }
 
-    private boolean configureDhcp(boolean enable, Inet4Address addr, int prefixLen) {
+    private boolean configureDhcp(boolean enable, final LinkAddress serverAddr,
+            final LinkAddress clientAddr) {
         if (enable) {
-            return startDhcp(addr, prefixLen);
+            return startDhcp(serverAddr, clientAddr);
         } else {
             stopDhcp();
             return true;
@@ -606,7 +615,7 @@
                 // code that calls into NetworkManagementService directly.
                 srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR);
                 mIpv4Address = new LinkAddress(srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
-                return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
+                return configureDhcp(enabled, mIpv4Address, null /* clientAddress */);
             }
             mIpv4Address = new LinkAddress(srvAddr, prefixLen);
         } catch (IllegalArgumentException e) {
@@ -643,7 +652,7 @@
             mLinkProperties.removeRoute(route);
         }
 
-        return configureDhcp(enabled, srvAddr, prefixLen);
+        return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr);
     }
 
     private String getRandomWifiIPv4Address() {
@@ -962,7 +971,14 @@
     }
 
     private void maybeConfigureStaticIp(final TetheringRequestParcel request) {
-        if (request == null) return;
+        // Ignore static address configuration if they are invalid or null. In theory, static
+        // addresses should not be invalid here because TetheringManager do not allow caller to
+        // specify invalid static address configuration.
+        if (request == null || request.localIPv4Address == null
+                || request.staticClientAddress == null || !checkStaticAddressConfiguration(
+                request.localIPv4Address, request.staticClientAddress)) {
+            return;
+        }
 
         mStaticIpv4ServerAddr = request.localIPv4Address;
         mStaticIpv4ClientAddr = request.staticClientAddress;
diff --git a/packages/Tethering/tests/integration/Android.bp b/packages/Tethering/tests/integration/Android.bp
index 1a1c30d..620261b 100644
--- a/packages/Tethering/tests/integration/Android.bp
+++ b/packages/Tethering/tests/integration/Android.bp
@@ -39,4 +39,9 @@
         "android.test.base",
         "android.test.mock",
     ],
+    jni_libs: [
+        // For mockito extended
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
 }
diff --git a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
index dbd68ef..b02bb23 100644
--- a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
+++ b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java
@@ -205,7 +205,7 @@
                 requestWithStaticIpv4(localAddr, clientAddr));
 
         mTetheringEventCallback.awaitInterfaceTethered();
-        assertInterfaceHasIpAddress(iface, clientAddr);
+        assertInterfaceHasIpAddress(iface, localAddr);
 
         byte[] client1 = MacAddress.fromString("1:2:3:4:5:6").toByteArray();
         byte[] client2 = MacAddress.fromString("a:b:c:d:e:f").toByteArray();
diff --git a/packages/Tethering/tests/unit/AndroidManifest.xml b/packages/Tethering/tests/unit/AndroidManifest.xml
index 530bc07..4ff1d37 100644
--- a/packages/Tethering/tests/unit/AndroidManifest.xml
+++ b/packages/Tethering/tests/unit/AndroidManifest.xml
@@ -20,7 +20,16 @@
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
+        <service
+            android:name="com.android.server.connectivity.tethering.MockTetheringService"
+            android:permission="android.permission.TETHER_PRIVILEGED"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.server.connectivity.tethering.TetheringService"/>
+            </intent-filter>
+        </service>
     </application>
+
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.networkstack.tethering.tests.unit"
         android:label="Tethering service tests">
diff --git a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
index e8add98..f8eb147 100644
--- a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
@@ -42,7 +42,9 @@
 @SmallTest
 public class DhcpServingParamsParcelExtTest {
     private static final Inet4Address TEST_ADDRESS = inet4Addr("192.168.0.123");
+    private static final Inet4Address TEST_CLIENT_ADDRESS = inet4Addr("192.168.0.42");
     private static final int TEST_ADDRESS_PARCELED = 0xc0a8007b;
+    private static final int TEST_CLIENT_ADDRESS_PARCELED = 0xc0a8002a;
     private static final int TEST_PREFIX_LENGTH = 17;
     private static final int TEST_LEASE_TIME_SECS = 120;
     private static final int TEST_MTU = 1000;
@@ -105,6 +107,12 @@
         assertFalse(mParcel.metered);
     }
 
+    @Test
+    public void testSetClientAddr() {
+        mParcel.setSingleClientAddr(TEST_CLIENT_ADDRESS);
+        assertEquals(TEST_CLIENT_ADDRESS_PARCELED, mParcel.clientAddr);
+    }
+
     private static Inet4Address inet4Addr(String addr) {
         return (Inet4Address) parseNumericAddress(addr);
     }
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index b3a30ab..6695eed 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -33,7 +33,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
@@ -72,8 +71,6 @@
 import org.mockito.quality.Strictness;
 
 import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -255,19 +252,16 @@
 
     @Test
     public void testRequestLastEntitlementCacheValue() throws Exception {
-        final CountDownLatch mCallbacklatch = new CountDownLatch(1);
         // 1. Entitlement check is not required.
         mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
         ResultReceiver receiver = new ResultReceiver(null) {
             @Override
             protected void onReceiveResult(int resultCode, Bundle resultData) {
                 assertEquals(TETHER_ERROR_NO_ERROR, resultCode);
-                mCallbacklatch.countDown();
             }
         };
         mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
         mLooper.dispatchAll();
-        callbackTimeoutHelper(mCallbacklatch);
         assertEquals(0, mEnMgr.uiProvisionCount);
         mEnMgr.reset();
 
@@ -277,12 +271,10 @@
             @Override
             protected void onReceiveResult(int resultCode, Bundle resultData) {
                 assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode);
-                mCallbacklatch.countDown();
             }
         };
         mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
         mLooper.dispatchAll();
-        callbackTimeoutHelper(mCallbacklatch);
         assertEquals(0, mEnMgr.uiProvisionCount);
         mEnMgr.reset();
         // 3. No cache value and ui entitlement check is needed.
@@ -291,12 +283,10 @@
             @Override
             protected void onReceiveResult(int resultCode, Bundle resultData) {
                 assertEquals(TETHER_ERROR_PROVISIONING_FAILED, resultCode);
-                mCallbacklatch.countDown();
             }
         };
         mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
         mLooper.dispatchAll();
-        callbackTimeoutHelper(mCallbacklatch);
         assertEquals(1, mEnMgr.uiProvisionCount);
         mEnMgr.reset();
         // 4. Cache value is TETHER_ERROR_PROVISIONING_FAILED and don't need to run entitlement
@@ -306,12 +296,10 @@
             @Override
             protected void onReceiveResult(int resultCode, Bundle resultData) {
                 assertEquals(TETHER_ERROR_PROVISIONING_FAILED, resultCode);
-                mCallbacklatch.countDown();
             }
         };
         mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
         mLooper.dispatchAll();
-        callbackTimeoutHelper(mCallbacklatch);
         assertEquals(0, mEnMgr.uiProvisionCount);
         mEnMgr.reset();
         // 5. Cache value is TETHER_ERROR_PROVISIONING_FAILED and ui entitlement check is needed.
@@ -320,12 +308,10 @@
             @Override
             protected void onReceiveResult(int resultCode, Bundle resultData) {
                 assertEquals(TETHER_ERROR_NO_ERROR, resultCode);
-                mCallbacklatch.countDown();
             }
         };
         mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
         mLooper.dispatchAll();
-        callbackTimeoutHelper(mCallbacklatch);
         assertEquals(1, mEnMgr.uiProvisionCount);
         mEnMgr.reset();
         // 6. Cache value is TETHER_ERROR_NO_ERROR.
@@ -334,12 +320,10 @@
             @Override
             protected void onReceiveResult(int resultCode, Bundle resultData) {
                 assertEquals(TETHER_ERROR_NO_ERROR, resultCode);
-                mCallbacklatch.countDown();
             }
         };
         mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
         mLooper.dispatchAll();
-        callbackTimeoutHelper(mCallbacklatch);
         assertEquals(0, mEnMgr.uiProvisionCount);
         mEnMgr.reset();
         // 7. Test get value for other downstream type.
@@ -347,12 +331,10 @@
             @Override
             protected void onReceiveResult(int resultCode, Bundle resultData) {
                 assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode);
-                mCallbacklatch.countDown();
             }
         };
         mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_USB, receiver, false);
         mLooper.dispatchAll();
-        callbackTimeoutHelper(mCallbacklatch);
         assertEquals(0, mEnMgr.uiProvisionCount);
         mEnMgr.reset();
         // 8. Test get value for invalid downstream type.
@@ -361,22 +343,14 @@
             @Override
             protected void onReceiveResult(int resultCode, Bundle resultData) {
                 assertEquals(TETHER_ERROR_ENTITLEMENT_UNKNOWN, resultCode);
-                mCallbacklatch.countDown();
             }
         };
         mEnMgr.requestLatestTetheringEntitlementResult(TETHERING_WIFI_P2P, receiver, true);
         mLooper.dispatchAll();
-        callbackTimeoutHelper(mCallbacklatch);
         assertEquals(0, mEnMgr.uiProvisionCount);
         mEnMgr.reset();
     }
 
-    void callbackTimeoutHelper(final CountDownLatch latch) throws Exception {
-        if (!latch.await(1, TimeUnit.SECONDS)) {
-            fail("Timout, fail to receive callback");
-        }
-    }
-
     @Test
     public void verifyPermissionResult() {
         setupForRequiredProvisioning();
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java
new file mode 100644
index 0000000..355ece9
--- /dev/null
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/MockTetheringService.java
@@ -0,0 +1,56 @@
+/*
+ * 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.connectivity.tethering;
+
+import static org.mockito.Mockito.mock;
+
+import android.content.Intent;
+import android.net.ITetheringConnector;
+import android.os.Binder;
+import android.os.IBinder;
+
+public class MockTetheringService extends TetheringService {
+    private final Tethering mTethering = mock(Tethering.class);
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new MockTetheringConnector(super.onBind(intent));
+    }
+
+    @Override
+    public Tethering makeTethering(TetheringDependencies deps) {
+        return mTethering;
+    }
+
+    public Tethering getTethering() {
+        return mTethering;
+    }
+
+    public class MockTetheringConnector extends Binder {
+        final IBinder mBase;
+        MockTetheringConnector(IBinder base) {
+            mBase = base;
+        }
+
+        public ITetheringConnector getTetheringConnector() {
+            return ITetheringConnector.Stub.asInterface(mBase);
+        }
+
+        public MockTetheringService getService() {
+            return MockTetheringService.this;
+        }
+    }
+}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java
new file mode 100644
index 0000000..d9d3e73
--- /dev/null
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringServiceTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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.connectivity.tethering;
+
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.net.IIntResultListener;
+import android.net.ITetheringConnector;
+import android.net.ITetheringEventCallback;
+import android.net.TetheringRequestParcel;
+import android.os.ResultReceiver;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.connectivity.tethering.MockTetheringService.MockTetheringConnector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class TetheringServiceTest {
+    private static final String TEST_IFACE_NAME = "test_wlan0";
+    private static final String TEST_CALLER_PKG = "test_pkg";
+    @Mock private ITetheringEventCallback mITetheringEventCallback;
+    @Rule public ServiceTestRule mServiceTestRule;
+    private Tethering mTethering;
+    private Intent mMockServiceIntent;
+    private ITetheringConnector mTetheringConnector;
+
+    private class TestTetheringResult extends IIntResultListener.Stub {
+        private int mResult = -1; // Default value that does not match any result code.
+        @Override
+        public void onResult(final int resultCode) {
+            mResult = resultCode;
+        }
+
+        public void assertResult(final int expected) {
+            assertEquals(expected, mResult);
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mServiceTestRule = new ServiceTestRule();
+        mMockServiceIntent = new Intent(
+                InstrumentationRegistry.getTargetContext(),
+                MockTetheringService.class);
+        final MockTetheringConnector mockConnector =
+                (MockTetheringConnector) mServiceTestRule.bindService(mMockServiceIntent);
+        mTetheringConnector = mockConnector.getTetheringConnector();
+        final MockTetheringService service = mockConnector.getService();
+        mTethering = service.getTethering();
+        verify(mTethering).startStateMachineUpdaters();
+        when(mTethering.hasTetherableConfiguration()).thenReturn(true);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mServiceTestRule.unbindService();
+    }
+
+    @Test
+    public void testTether() throws Exception {
+        when(mTethering.tether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR);
+        final TestTetheringResult result = new TestTetheringResult();
+        mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, result);
+        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).tether(TEST_IFACE_NAME);
+        verifyNoMoreInteractions(mTethering);
+        result.assertResult(TETHER_ERROR_NO_ERROR);
+    }
+
+    @Test
+    public void testUntether() throws Exception {
+        when(mTethering.untether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR);
+        final TestTetheringResult result = new TestTetheringResult();
+        mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, result);
+        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).untether(TEST_IFACE_NAME);
+        verifyNoMoreInteractions(mTethering);
+        result.assertResult(TETHER_ERROR_NO_ERROR);
+    }
+
+    @Test
+    public void testSetUsbTethering() throws Exception {
+        when(mTethering.setUsbTethering(true /* enable */)).thenReturn(TETHER_ERROR_NO_ERROR);
+        final TestTetheringResult result = new TestTetheringResult();
+        mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, result);
+        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).setUsbTethering(true /* enable */);
+        verifyNoMoreInteractions(mTethering);
+        result.assertResult(TETHER_ERROR_NO_ERROR);
+    }
+
+    @Test
+    public void testStartTethering() throws Exception {
+        final TestTetheringResult result = new TestTetheringResult();
+        final TetheringRequestParcel request = new TetheringRequestParcel();
+        request.tetheringType = TETHERING_WIFI;
+        mTetheringConnector.startTethering(request, TEST_CALLER_PKG, result);
+        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).startTethering(eq(request), eq(result));
+        verifyNoMoreInteractions(mTethering);
+    }
+
+    @Test
+    public void testStopTethering() throws Exception {
+        final TestTetheringResult result = new TestTetheringResult();
+        mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, result);
+        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).stopTethering(TETHERING_WIFI);
+        verifyNoMoreInteractions(mTethering);
+        result.assertResult(TETHER_ERROR_NO_ERROR);
+    }
+
+    @Test
+    public void testRequestLatestTetheringEntitlementResult() throws Exception {
+        final ResultReceiver result = new ResultReceiver(null);
+        mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
+                true /* showEntitlementUi */, TEST_CALLER_PKG);
+        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).requestLatestTetheringEntitlementResult(eq(TETHERING_WIFI),
+                eq(result), eq(true) /* showEntitlementUi */);
+        verifyNoMoreInteractions(mTethering);
+    }
+
+    @Test
+    public void testRegisterTetheringEventCallback() throws Exception {
+        mTetheringConnector.registerTetheringEventCallback(mITetheringEventCallback,
+                TEST_CALLER_PKG);
+        verify(mTethering).registerTetheringEventCallback(eq(mITetheringEventCallback));
+        verifyNoMoreInteractions(mTethering);
+    }
+
+    @Test
+    public void testUnregisterTetheringEventCallback() throws Exception {
+        mTetheringConnector.unregisterTetheringEventCallback(mITetheringEventCallback,
+                TEST_CALLER_PKG);
+        verify(mTethering).unregisterTetheringEventCallback(
+                eq(mITetheringEventCallback));
+        verifyNoMoreInteractions(mTethering);
+    }
+
+    @Test
+    public void testStopAllTethering() throws Exception {
+        final TestTetheringResult result = new TestTetheringResult();
+        mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, result);
+        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).untetherAll();
+        verifyNoMoreInteractions(mTethering);
+        result.assertResult(TETHER_ERROR_NO_ERROR);
+    }
+
+    @Test
+    public void testIsTetheringSupported() throws Exception {
+        final TestTetheringResult result = new TestTetheringResult();
+        mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, result);
+        verify(mTethering).hasTetherableConfiguration();
+        verifyNoMoreInteractions(mTethering);
+        result.assertResult(TETHER_ERROR_NO_ERROR);
+    }
+}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index a59c6fd..2955903c 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -38,6 +38,7 @@
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
@@ -140,7 +141,9 @@
 import com.android.testutils.MiscAssertsKt;
 
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -439,6 +442,18 @@
         return buildMobileUpstreamState(false, true, true);
     }
 
+    // See FakeSettingsProvider#clearSettingsProvider() that this needs to be called before and
+    // after use.
+    @BeforeClass
+    public static void setupOnce() {
+        FakeSettingsProvider.clearSettingsProvider();
+    }
+
+    @AfterClass
+    public static void tearDownOnce() {
+        FakeSettingsProvider.clearSettingsProvider();
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -1654,10 +1669,13 @@
     }
 
     @Test
-    public void testRequestStaticServerIp() throws Exception {
-        final LinkAddress serverLinkAddr = new LinkAddress("192.168.20.1/24");
-        final LinkAddress clientLinkAddr = new LinkAddress("192.168.20.42/24");
-        final String serverAddr = "192.168.20.1";
+    public void testRequestStaticIp() throws Exception {
+        final LinkAddress serverLinkAddr = new LinkAddress("192.168.0.123/24");
+        final LinkAddress clientLinkAddr = new LinkAddress("192.168.0.42/24");
+        final String serverAddr = "192.168.0.123";
+        final int clientAddrParceled = 0xc0a8002a;
+        final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
+                ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
         mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
                   serverLinkAddr, clientLinkAddr), null);
         mLooper.dispatchAll();
@@ -1666,8 +1684,12 @@
         sendUsbBroadcast(true, true, true, TETHERING_USB);
         mLooper.dispatchAll();
         verify(mNetd).interfaceSetCfg(argThat(cfg -> serverAddr.equals(cfg.ipv4Addr)));
-
-        // TODO: test static client address.
+        verify(mIpServerDependencies, times(1)).makeDhcpServer(any(), dhcpParamsCaptor.capture(),
+                any());
+        final DhcpServingParamsParcel params = dhcpParamsCaptor.getValue();
+        assertEquals(serverAddr, intToInet4AddressHTH(params.serverAddr).getHostAddress());
+        assertEquals(24, params.serverAddrPrefixLength);
+        assertEquals(clientAddrParceled, params.clientAddr);
     }
 
     // TODO: Test that a request for hotspot mode doesn't interfere with an
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 93bffe9..52a82dd 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -36,10 +36,12 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Slog;
 import android.util.Xml;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.PackageMonitor;
 
 import libcore.io.IoUtils;
 
@@ -261,22 +263,7 @@
 
             // And reset to the wallpaper service we should be using
             ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
-            if (servicePackageExists(wpService)) {
-                Slog.i(TAG, "Using wallpaper service " + wpService);
-                mWm.setWallpaperComponent(wpService, UserHandle.USER_SYSTEM);
-                if (!lockImageStage.exists()) {
-                    // We have a live wallpaper and no static lock image,
-                    // allow live wallpaper to show "through" on lock screen.
-                    mWm.clear(FLAG_LOCK);
-                }
-            } else {
-                // If we've restored a live wallpaper, but the component doesn't exist,
-                // we should log it as an error so we can easily identify the problem
-                // in reports from users
-                if (wpService != null) {
-                    Slog.e(TAG, "Wallpaper service " + wpService + " isn't available.");
-                }
-            }
+            updateWallpaperComponent(wpService, !lockImageStage.exists());
         } catch (Exception e) {
             Slog.e(TAG, "Unable to restore wallpaper: " + e.getMessage());
         } finally {
@@ -293,6 +280,28 @@
         }
     }
 
+    @VisibleForTesting
+    void updateWallpaperComponent(ComponentName wpService, boolean applyToLock) throws IOException {
+        if (servicePackageExists(wpService)) {
+            Slog.i(TAG, "Using wallpaper service " + wpService);
+            mWm.setWallpaperComponent(wpService, UserHandle.USER_SYSTEM);
+            if (applyToLock) {
+                // We have a live wallpaper and no static lock image,
+                // allow live wallpaper to show "through" on lock screen.
+                mWm.clear(FLAG_LOCK);
+            }
+        } else {
+            // If we've restored a live wallpaper, but the component doesn't exist,
+            // we should log it as an error so we can easily identify the problem
+            // in reports from users
+            if (wpService != null) {
+                applyComponentAtInstall(wpService, applyToLock);
+                Slog.w(TAG, "Wallpaper service " + wpService + " isn't available. "
+                        + " Will try to apply later");
+            }
+        }
+    }
+
     private void restoreFromStage(File stage, File info, String hintTag, int which)
             throws IOException {
         if (stage.exists()) {
@@ -372,7 +381,8 @@
         return (value == null) ? defValue : Integer.parseInt(value);
     }
 
-    private boolean servicePackageExists(ComponentName comp) {
+    @VisibleForTesting
+    boolean servicePackageExists(ComponentName comp) {
         try {
             if (comp != null) {
                 final IPackageManager pm = AppGlobals.getPackageManager();
@@ -401,4 +411,53 @@
             throws IOException {
         // Intentionally blank
     }
+
+    private void applyComponentAtInstall(ComponentName componentName, boolean applyToLock) {
+        PackageMonitor packageMonitor = getWallpaperPackageMonitor(componentName, applyToLock);
+        packageMonitor.register(getBaseContext(), null, UserHandle.ALL, true);
+    }
+
+    @VisibleForTesting
+    PackageMonitor getWallpaperPackageMonitor(ComponentName componentName, boolean applyToLock) {
+        return new PackageMonitor() {
+            @Override
+            public void onPackageAdded(String packageName, int uid) {
+                if (!isDeviceInRestore()) {
+                    // We don't want to reapply the wallpaper outside a restore.
+                    unregister();
+                    return;
+                }
+
+                if (componentName.getPackageName().equals(packageName)) {
+                    Slog.d(TAG, "Applying component " + componentName);
+                    mWm.setWallpaperComponent(componentName);
+                    if (applyToLock) {
+                        try {
+                            mWm.clear(FLAG_LOCK);
+                        } catch (IOException e) {
+                            Slog.w(TAG, "Failed to apply live wallpaper to lock screen: " + e);
+                        }
+                    }
+                    // We're only expecting to restore the wallpaper component once.
+                    unregister();
+                }
+            }
+        };
+    }
+
+    @VisibleForTesting
+    boolean isDeviceInRestore() {
+        try {
+            boolean isInSetup = Settings.Secure.getInt(getBaseContext().getContentResolver(),
+                    Settings.Secure.USER_SETUP_COMPLETE) == 0;
+            boolean isInDeferredSetup = Settings.Secure.getInt(getBaseContext()
+                            .getContentResolver(),
+                    Settings.Secure.USER_SETUP_PERSONALIZATION_STATE) ==
+                    Settings.Secure.USER_SETUP_PERSONALIZATION_STARTED;
+            return isInSetup || isInDeferredSetup;
+        } catch (Settings.SettingNotFoundException e) {
+            Slog.w(TAG, "Failed to check if the user is in restore: " + e);
+            return false;
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
similarity index 65%
rename from packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java
rename to packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 255fdef..4367075 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/tests/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wallpaperbackup.tests;
+package com.android.wallpaperbackup;
 
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.FLAG_SYSTEM;
@@ -26,17 +26,22 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.WallpaperManager;
 import android.app.backup.FullBackupDataOutput;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.UserHandle;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.content.PackageMonitor;
 import com.android.wallpaperbackup.WallpaperBackupAgent;
 import com.android.wallpaperbackup.utils.ContextWithServiceOverrides;
 
@@ -58,6 +63,7 @@
 public class WallpaperBackupAgentTest {
     private static final String SYSTEM_GENERATION = "system_gen";
     private static final String LOCK_GENERATION = "lock_gen";
+    private static final String TEST_WALLPAPER_PACKAGE = "wallpaper_package";
 
     private static final int TEST_SYSTEM_WALLPAPER_ID = 1;
     private static final int TEST_LOCK_WALLPAPER_ID = 2;
@@ -66,11 +72,13 @@
     @Mock private WallpaperManager mWallpaperManager;
     @Mock private SharedPreferences mSharedPreferences;
     @Mock private SharedPreferences.Editor mSharedPreferenceEditor;
+    @Mock private Context mMockContext;
 
     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
 
     private ContextWithServiceOverrides mContext;
     private IsolatedWallpaperBackupAgent mWallpaperBackupAgent;
+    private ComponentName mWallpaperComponent;
 
     @Before
     public void setUp() {
@@ -88,6 +96,8 @@
         mWallpaperBackupAgent = new IsolatedWallpaperBackupAgent(mTemporaryFolder.getRoot());
         mWallpaperBackupAgent.attach(mContext);
         mWallpaperBackupAgent.onCreate();
+
+        mWallpaperComponent = new ComponentName(TEST_WALLPAPER_PACKAGE, "");
     }
 
     @Test
@@ -130,6 +140,69 @@
         inOrder.verify(mSharedPreferenceEditor).apply();
     }
 
+    @Test
+    public void updateWallpaperComponent_doesApplyLater() throws IOException {
+        mWallpaperBackupAgent.mIsDeviceInRestore = true;
+
+        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+                /* applyToLock */ true);
+
+        // Imitate wallpaper component installation.
+        mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+                /* uid */0);
+
+        verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
+        verify(mWallpaperManager, times(1)).clear(eq(FLAG_LOCK));
+    }
+
+    @Test
+    public void updateWallpaperComponent_applyToLockFalse_doesApplyLaterOnlyToMainScreen()
+            throws IOException {
+        mWallpaperBackupAgent.mIsDeviceInRestore = true;
+
+        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+                /* applyToLock */ false);
+
+        // Imitate wallpaper component installation.
+        mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+                /* uid */0);
+
+        verify(mWallpaperManager, times(1)).setWallpaperComponent(mWallpaperComponent);
+        verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
+    }
+
+    @Test
+    public void updateWallpaperComponent_deviceNotInRestore_doesNotApply()
+            throws IOException {
+        mWallpaperBackupAgent.mIsDeviceInRestore = false;
+
+        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+                /* applyToLock */ true);
+
+        // Imitate wallpaper component installation.
+        mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(TEST_WALLPAPER_PACKAGE,
+                /* uid */0);
+
+        verify(mWallpaperManager, never()).setWallpaperComponent(mWallpaperComponent);
+        verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
+    }
+
+    @Test
+    public void updateWallpaperComponent_differentPackageInstalled_doesNotApply()
+            throws IOException {
+        mWallpaperBackupAgent.mIsDeviceInRestore = false;
+
+        mWallpaperBackupAgent.updateWallpaperComponent(mWallpaperComponent,
+                /* applyToLock */ true);
+
+        // Imitate "wrong" wallpaper component installation.
+        mWallpaperBackupAgent.mWallpaperPackageMonitor.onPackageAdded(/* packageName */"",
+                /* uid */0);
+
+        verify(mWallpaperManager, never()).setWallpaperComponent(mWallpaperComponent);
+        verify(mWallpaperManager, never()).clear(eq(FLAG_LOCK));
+    }
+
     private void mockUnbackedUpState() {
         mockCurrentWallpapers(TEST_SYSTEM_WALLPAPER_ID, TEST_LOCK_WALLPAPER_ID);
         when(mSharedPreferences.getInt(eq(SYSTEM_GENERATION), eq(-1))).thenReturn(-1);
@@ -162,6 +235,8 @@
     private class IsolatedWallpaperBackupAgent extends WallpaperBackupAgent {
         File mWallpaperBaseDirectory;
         List<File> mBackedUpFiles = new ArrayList<>();
+        PackageMonitor mWallpaperPackageMonitor;
+        boolean mIsDeviceInRestore = false;
 
         IsolatedWallpaperBackupAgent(File wallpaperBaseDirectory) {
             mWallpaperBaseDirectory = wallpaperBaseDirectory;
@@ -181,5 +256,27 @@
         public SharedPreferences getSharedPreferences(File file, int mode) {
             return mSharedPreferences;
         }
+
+        @Override
+        boolean servicePackageExists(ComponentName comp) {
+            return false;
+        }
+
+        @Override
+        boolean isDeviceInRestore() {
+            return mIsDeviceInRestore;
+        }
+
+        @Override
+        PackageMonitor getWallpaperPackageMonitor(ComponentName componentName,
+                boolean applyToLock) {
+            mWallpaperPackageMonitor = super.getWallpaperPackageMonitor(componentName, applyToLock);
+            return mWallpaperPackageMonitor;
+        }
+
+        @Override
+        public Context getBaseContext() {
+            return mMockContext;
+        }
     }
 }
diff --git a/proto/src/typed_features.proto b/proto/src/typed_features.proto
new file mode 100644
index 0000000..c2b3b18
--- /dev/null
+++ b/proto/src/typed_features.proto
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.service;
+option java_multiple_files = true;
+
+// This message is to specify feature params that are a list of strings.
+message StringListParamProto {
+  repeated string element = 1;
+}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 2100c1a..7230b00 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -413,7 +413,12 @@
                                     && component.getPackageName().equals(packageName))
                             || userState.mCrashedServices.removeIf(component -> component != null
                                     && component.getPackageName().equals(packageName));
-                    if (reboundAService) {
+                    // Reloads the installed services info to make sure the rebound service could
+                    // get a new one.
+                    userState.mInstalledServices.clear();
+                    final boolean configurationChanged =
+                            readConfigurationForUserStateLocked(userState);
+                    if (reboundAService || configurationChanged) {
                         onUserStateChangedLocked(userState);
                     }
                     migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
index ff59c24..20a11bd 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -17,6 +17,7 @@
 package com.android.server.accessibility;
 
 import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.os.ShellCommand;
 import android.os.UserHandle;
 
@@ -83,7 +84,7 @@
                 return null;
             }
         }
-        return UserHandle.USER_SYSTEM;
+        return ActivityManager.getCurrentUser();
     }
 
     @Override
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 8c1360c..ca1b27b 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -3669,6 +3669,9 @@
             // Default launcher from package manager.
             final ComponentName defaultLauncher = mPackageManagerInternal
                     .getDefaultHomeActivity(UserHandle.getUserId(callingUid));
+            if (defaultLauncher == null) {
+                return;
+            }
             int defaultLauncherUid  = 0;
             try {
                 defaultLauncherUid = mPackageManager.getApplicationInfo(
diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java
new file mode 100644
index 0000000..3612e09
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSessionController.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.inputmethod.InputMethodManagerInternal;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+
+/**
+ * Controls the interaction with the IME for the inline suggestion sessions.
+ */
+final class AutofillInlineSessionController {
+    @NonNull
+    private final InputMethodManagerInternal mInputMethodManagerInternal;
+    private final int mUserId;
+    @NonNull
+    private final ComponentName mComponentName;
+    @NonNull
+    private final Object mLock;
+    @NonNull
+    private final Handler mHandler;
+
+    @GuardedBy("mLock")
+    private AutofillInlineSuggestionsRequestSession mSession;
+
+    AutofillInlineSessionController(InputMethodManagerInternal inputMethodManagerInternal,
+            int userId, ComponentName componentName, Handler handler, Object lock) {
+        mInputMethodManagerInternal = inputMethodManagerInternal;
+        mUserId = userId;
+        mComponentName = componentName;
+        mHandler = handler;
+        mLock = lock;
+    }
+
+
+    /**
+     * Requests the IME to create an {@link InlineSuggestionsRequest} for {@code autofillId}.
+     *
+     * @param autofillId      the Id of the field for which the request is for.
+     * @param requestConsumer the callback which will be invoked when IME responded or if it times
+     *                        out waiting for IME response.
+     */
+    @GuardedBy("mLock")
+    void onCreateInlineSuggestionsRequestLocked(@NonNull AutofillId autofillId,
+            @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) {
+        // TODO(b/151123764): rename the method to better reflect what it does.
+        if (mSession != null) {
+            // Send an empty response to IME and destroy the existing session.
+            mSession.onInlineSuggestionsResponseLocked(mSession.getAutofillIdLocked(),
+                    new InlineSuggestionsResponse(Collections.EMPTY_LIST));
+            mSession.destroySessionLocked();
+        }
+        // TODO(b/151123764): consider reusing the same AutofillInlineSession object for the
+        // same field.
+        mSession = new AutofillInlineSuggestionsRequestSession(mInputMethodManagerInternal, mUserId,
+                mComponentName, mHandler, mLock, autofillId, requestConsumer, uiExtras);
+        mSession.onCreateInlineSuggestionsRequestLocked();
+
+    }
+
+    /**
+     * Returns the {@link InlineSuggestionsRequest} provided by IME for the last request.
+     *
+     * <p> The caller is responsible for making sure Autofill hears back from IME before calling
+     * this method, using the {@code requestConsumer} provided when calling {@link
+     * #onCreateInlineSuggestionsRequestLocked(AutofillId, Consumer, Bundle)}.
+     */
+    @GuardedBy("mLock")
+    Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() {
+        if (mSession != null) {
+            return mSession.getInlineSuggestionsRequestLocked();
+        }
+        return Optional.empty();
+    }
+
+    /**
+     * Requests the IME to hide the current suggestions, if any. Returns true if the message is sent
+     * to the IME.
+     */
+    @GuardedBy("mLock")
+    boolean hideInlineSuggestionsUiLocked(@NonNull AutofillId autofillId) {
+        if (mSession != null) {
+            return mSession.onInlineSuggestionsResponseLocked(autofillId,
+                    new InlineSuggestionsResponse(Collections.EMPTY_LIST));
+        }
+        return false;
+    }
+
+    /**
+     * Requests showing the inline suggestion in the IME when the IME becomes visible and is focused
+     * on the {@code autofillId}.
+     *
+     * @return false if there is no session, or if the IME callback is not available in the session.
+     */
+    @GuardedBy("mLock")
+    boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
+            @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
+        // TODO(b/151123764): rename the method to better reflect what it does.
+        if (mSession != null) {
+            return mSession.onInlineSuggestionsResponseLocked(autofillId,
+                    inlineSuggestionsResponse);
+        }
+        return false;
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
new file mode 100644
index 0000000..ca230b6
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
@@ -0,0 +1,419 @@
+/*
+ * 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.autofill;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.autofill.Helper.sDebug;
+
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.view.autofill.AutofillId;
+import android.view.inputmethod.InlineSuggestionsRequest;
+import android.view.inputmethod.InlineSuggestionsResponse;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
+import com.android.internal.view.InlineSuggestionsRequestInfo;
+import com.android.server.inputmethod.InputMethodManagerInternal;
+
+import java.lang.ref.WeakReference;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+/**
+ * Maintains an inline suggestion session with the IME.
+ *
+ * <p> Each session corresponds to one request from the Autofill manager service to create an
+ * {@link InlineSuggestionsRequest}. It's responsible for receiving callbacks from the IME and
+ * sending {@link android.view.inputmethod.InlineSuggestionsResponse} to IME.
+ */
+final class AutofillInlineSuggestionsRequestSession {
+
+    private static final String TAG = AutofillInlineSuggestionsRequestSession.class.getSimpleName();
+    private static final int INLINE_REQUEST_TIMEOUT_MS = 200;
+
+    @NonNull
+    private final InputMethodManagerInternal mInputMethodManagerInternal;
+    private final int mUserId;
+    @NonNull
+    private final ComponentName mComponentName;
+    @NonNull
+    private final Object mLock;
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final Bundle mUiExtras;
+
+    @GuardedBy("mLock")
+    @NonNull
+    private AutofillId mAutofillId;
+    @GuardedBy("mLock")
+    @Nullable
+    private Consumer<InlineSuggestionsRequest> mImeRequestConsumer;
+
+    @GuardedBy("mLock")
+    private boolean mImeRequestReceived;
+    @GuardedBy("mLock")
+    @Nullable
+    private InlineSuggestionsRequest mImeRequest;
+    @GuardedBy("mLock")
+    @Nullable
+    private IInlineSuggestionsResponseCallback mResponseCallback;
+    @GuardedBy("mLock")
+    @Nullable
+    private Runnable mTimeoutCallback;
+
+    @GuardedBy("mLock")
+    @Nullable
+    private AutofillId mImeCurrentFieldId;
+    @GuardedBy("mLock")
+    private boolean mImeInputStarted;
+    @GuardedBy("mLock")
+    private boolean mImeInputViewStarted;
+    @GuardedBy("mLock")
+    @Nullable
+    private InlineSuggestionsResponse mInlineSuggestionsResponse;
+    @GuardedBy("mLock")
+    private boolean mPreviousResponseIsNotEmpty;
+
+    @GuardedBy("mLock")
+    private boolean mDestroyed = false;
+
+    AutofillInlineSuggestionsRequestSession(
+            @NonNull InputMethodManagerInternal inputMethodManagerInternal, int userId,
+            @NonNull ComponentName componentName, @NonNull Handler handler, @NonNull Object lock,
+            @NonNull AutofillId autofillId,
+            @NonNull Consumer<InlineSuggestionsRequest> requestConsumer, @NonNull Bundle uiExtras) {
+        mInputMethodManagerInternal = inputMethodManagerInternal;
+        mUserId = userId;
+        mComponentName = componentName;
+        mHandler = handler;
+        mLock = lock;
+        mUiExtras = uiExtras;
+
+        mAutofillId = autofillId;
+        mImeRequestConsumer = requestConsumer;
+    }
+
+    @GuardedBy("mLock")
+    @NonNull
+    AutofillId getAutofillIdLocked() {
+        return mAutofillId;
+    }
+
+    /**
+     * Returns the {@link InlineSuggestionsRequest} provided by IME.
+     *
+     * <p> The caller is responsible for making sure Autofill hears back from IME before calling
+     * this method, using the {@link #mImeRequestConsumer}.
+     */
+    @GuardedBy("mLock")
+    Optional<InlineSuggestionsRequest> getInlineSuggestionsRequestLocked() {
+        if (mDestroyed) {
+            return Optional.empty();
+        }
+        return Optional.ofNullable(mImeRequest);
+    }
+
+    /**
+     * Requests showing the inline suggestion in the IME when the IME becomes visible and is focused
+     * on the {@code autofillId}.
+     *
+     * @return false if the IME callback is not available.
+     */
+    @GuardedBy("mLock")
+    boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
+            @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
+        if (mDestroyed) {
+            return false;
+        }
+        if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked called for:" + autofillId);
+        if (mImeRequest == null || mResponseCallback == null) {
+            return false;
+        }
+        // TODO(b/151123764): each session should only correspond to one field.
+        mAutofillId = autofillId;
+        mInlineSuggestionsResponse = inlineSuggestionsResponse;
+        maybeUpdateResponseToImeLocked();
+        return true;
+    }
+
+    /**
+     * This method must be called when the session is destroyed, to avoid further callbacks from/to
+     * the IME.
+     */
+    @GuardedBy("mLock")
+    void destroySessionLocked() {
+        mDestroyed = true;
+    }
+
+    /**
+     * Requests the IME to create an {@link InlineSuggestionsRequest}.
+     *
+     * <p> This method should only be called once per session.
+     */
+    @GuardedBy("mLock")
+    void onCreateInlineSuggestionsRequestLocked() {
+        if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequestLocked called: " + mAutofillId);
+        if (mDestroyed) {
+            return;
+        }
+        mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(mUserId,
+                new InlineSuggestionsRequestInfo(mComponentName, mAutofillId, mUiExtras),
+                new InlineSuggestionsRequestCallbackImpl(this));
+        mTimeoutCallback = () -> {
+            Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest.");
+            handleOnReceiveImeRequest(null, null);
+        };
+        mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS);
+    }
+
+    /**
+     * Optionally sends inline response to the IME, depending on the current state.
+     */
+    @GuardedBy("mLock")
+    private void maybeUpdateResponseToImeLocked() {
+        if (sDebug) Log.d(TAG, "maybeUpdateResponseToImeLocked called");
+        if (mDestroyed || mResponseCallback == null) {
+            return;
+        }
+        if (!mImeInputViewStarted && mPreviousResponseIsNotEmpty) {
+            // 1. if previous response is not empty, and IME just become invisible, then send
+            // empty response to make sure existing responses don't stick around on the IME.
+            // Although the inline suggestions should disappear when IME hides which removes them
+            // from the view hierarchy, but we still send an empty response to be extra safe.
+
+            // TODO(b/149945531): clear the existing suggestions when IME is hide, once the bug is
+            //  fixed.
+            //if (sDebug) Log.d(TAG, "Send empty inline response");
+            //updateResponseToImeUncheckLocked(new InlineSuggestionsResponse(Collections
+            // .EMPTY_LIST));
+            //mPreviousResponseIsNotEmpty = false;
+        } else if (mImeInputViewStarted && mInlineSuggestionsResponse != null && match(mAutofillId,
+                mImeCurrentFieldId)) {
+            // 2. if IME is visible, and response is not null, send the response
+            boolean isEmptyResponse = mInlineSuggestionsResponse.getInlineSuggestions().isEmpty();
+            if (isEmptyResponse && !mPreviousResponseIsNotEmpty) {
+                // No-op if both the previous response and current response are empty.
+                return;
+            }
+            if (sDebug) {
+                Log.d(TAG, "Send inline response: "
+                        + mInlineSuggestionsResponse.getInlineSuggestions().size());
+            }
+            updateResponseToImeUncheckLocked(mInlineSuggestionsResponse);
+            // TODO(b/149945531): don't set the response to null so it's cached, once the bug is
+            //  fixed.
+            mInlineSuggestionsResponse = null;
+            mPreviousResponseIsNotEmpty = !isEmptyResponse;
+        }
+    }
+
+    /**
+     * Sends the {@code response} to the IME, assuming all the relevant checks are already done.
+     */
+    @GuardedBy("mLock")
+    private void updateResponseToImeUncheckLocked(InlineSuggestionsResponse response) {
+        if (mDestroyed) {
+            return;
+        }
+        try {
+            mResponseCallback.onInlineSuggestionsResponse(mAutofillId, response);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME");
+        }
+    }
+
+    /**
+     * Handles the {@code request} and {@code callback} received from the IME.
+     *
+     * <p> Should only invoked in the {@link #mHandler} thread.
+     */
+    private void handleOnReceiveImeRequest(@Nullable InlineSuggestionsRequest request,
+            @Nullable IInlineSuggestionsResponseCallback callback) {
+        synchronized (mLock) {
+            if (mDestroyed || mImeRequestReceived) {
+                return;
+            }
+            mImeRequestReceived = true;
+
+            if (mTimeoutCallback != null) {
+                if (sDebug) Log.d(TAG, "removing timeout callback");
+                mHandler.removeCallbacks(mTimeoutCallback);
+                mTimeoutCallback = null;
+            }
+            if (request != null && callback != null) {
+                mImeRequest = request;
+                mResponseCallback = callback;
+                handleOnReceiveImeStatusUpdated(mAutofillId, true, false);
+            }
+            if (mImeRequestConsumer != null) {
+                // Note that mImeRequest is only set if both request and callback are non-null.
+                mImeRequestConsumer.accept(mImeRequest);
+                mImeRequestConsumer = null;
+            }
+        }
+    }
+
+    /**
+     * Handles the IME status updates received from the IME.
+     *
+     * <p> Should only be invoked in the {@link #mHandler} thread.
+     */
+    private void handleOnReceiveImeStatusUpdated(boolean imeInputStarted,
+            boolean imeInputViewStarted) {
+        synchronized (mLock) {
+            if (mDestroyed) {
+                return;
+            }
+            if (mImeCurrentFieldId != null) {
+                boolean imeInputStartedChanged = (mImeInputStarted != imeInputStarted);
+                boolean imeInputViewStartedChanged = (mImeInputViewStarted != imeInputViewStarted);
+                mImeInputStarted = imeInputStarted;
+                mImeInputViewStarted = imeInputViewStarted;
+                if (imeInputStartedChanged || imeInputViewStartedChanged) {
+                    maybeUpdateResponseToImeLocked();
+                }
+            }
+        }
+    }
+
+    /**
+     * Handles the IME status updates received from the IME.
+     *
+     * <p> Should only be invoked in the {@link #mHandler} thread.
+     */
+    private void handleOnReceiveImeStatusUpdated(@Nullable AutofillId imeFieldId,
+            boolean imeInputStarted, boolean imeInputViewStarted) {
+        synchronized (mLock) {
+            if (mDestroyed) {
+                return;
+            }
+            if (imeFieldId != null) {
+                mImeCurrentFieldId = imeFieldId;
+            }
+            handleOnReceiveImeStatusUpdated(imeInputStarted, imeInputViewStarted);
+        }
+    }
+
+    private static final class InlineSuggestionsRequestCallbackImpl extends
+            IInlineSuggestionsRequestCallback.Stub {
+
+        private final WeakReference<AutofillInlineSuggestionsRequestSession> mSession;
+
+        private InlineSuggestionsRequestCallbackImpl(
+                AutofillInlineSuggestionsRequestSession session) {
+            mSession = new WeakReference<>(session);
+        }
+
+        @BinderThread
+        @Override
+        public void onInlineSuggestionsUnsupported() throws RemoteException {
+            if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
+            final AutofillInlineSuggestionsRequestSession session = mSession.get();
+            if (session != null) {
+                session.mHandler.sendMessage(obtainMessage(
+                        AutofillInlineSuggestionsRequestSession::handleOnReceiveImeRequest, session,
+                        null, null));
+            }
+        }
+
+        @BinderThread
+        @Override
+        public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
+                IInlineSuggestionsResponseCallback callback) {
+            if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
+            final AutofillInlineSuggestionsRequestSession session = mSession.get();
+            if (session != null) {
+                session.mHandler.sendMessage(obtainMessage(
+                        AutofillInlineSuggestionsRequestSession::handleOnReceiveImeRequest, session,
+                        request, callback));
+            }
+        }
+
+        @Override
+        public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
+            if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + imeFieldId);
+            final AutofillInlineSuggestionsRequestSession session = mSession.get();
+            if (session != null) {
+                session.mHandler.sendMessage(obtainMessage(
+                        AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+                        session, imeFieldId, true, false));
+            }
+        }
+
+        @Override
+        public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
+            if (sDebug) {
+                Log.d(TAG, "onInputMethodShowInputRequested() received: " + requestResult);
+            }
+        }
+
+        @BinderThread
+        @Override
+        public void onInputMethodStartInputView() {
+            if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received");
+            final AutofillInlineSuggestionsRequestSession session = mSession.get();
+            if (session != null) {
+                session.mHandler.sendMessage(obtainMessage(
+                        AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+                        session, true, true));
+            }
+        }
+
+        @BinderThread
+        @Override
+        public void onInputMethodFinishInputView() {
+            if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received");
+            final AutofillInlineSuggestionsRequestSession session = mSession.get();
+            if (session != null) {
+                session.mHandler.sendMessage(obtainMessage(
+                        AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+                        session, true, false));
+            }
+        }
+
+        @Override
+        public void onInputMethodFinishInput() throws RemoteException {
+            if (sDebug) Log.d(TAG, "onInputMethodFinishInput() received");
+            final AutofillInlineSuggestionsRequestSession session = mSession.get();
+            if (session != null) {
+                session.mHandler.sendMessage(obtainMessage(
+                        AutofillInlineSuggestionsRequestSession::handleOnReceiveImeStatusUpdated,
+                        session, false, false));
+            }
+        }
+    }
+
+    private static boolean match(@Nullable AutofillId autofillId,
+            @Nullable AutofillId imeClientFieldId) {
+        // The IME doesn't have information about the virtual view id for the child views in the
+        // web view, so we are only comparing the parent view id here. This means that for cases
+        // where there are two input fields in the web view, they will have the same view id
+        // (although different virtual child id), and we will not be able to distinguish them.
+        return autofillId != null && imeClientFieldId != null
+                && autofillId.getViewId() == imeClientFieldId.getViewId();
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java b/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
deleted file mode 100644
index e2d5112..0000000
--- a/services/autofill/java/com/android/server/autofill/InlineSuggestionSession.java
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * 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.autofill;
-
-import static com.android.server.autofill.Helper.sDebug;
-
-import android.annotation.BinderThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Slog;
-import android.view.autofill.AutofillId;
-import android.view.inputmethod.InlineSuggestionsRequest;
-import android.view.inputmethod.InlineSuggestionsResponse;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.view.IInlineSuggestionsRequestCallback;
-import com.android.internal.view.IInlineSuggestionsResponseCallback;
-import com.android.internal.view.InlineSuggestionsRequestInfo;
-import com.android.server.inputmethod.InputMethodManagerInternal;
-
-import java.util.Collections;
-import java.util.Optional;
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Consumer;
-
-/**
- * Maintains an autofill inline suggestion session that communicates with the IME.
- *
- * <p>
- * The same session may be reused for multiple input fields involved in the same autofill
- * {@link Session}. Therefore, one {@link InlineSuggestionsRequest} and one
- * {@link IInlineSuggestionsResponseCallback} may be used to generate and callback with inline
- * suggestions for different input fields.
- *
- * <p>
- * This class is the sole place in Autofill responsible for directly communicating with the IME. It
- * receives the IME input view start/finish events, with the associated IME field Id. It uses the
- * information to decide when to send the {@link InlineSuggestionsResponse} to IME. As a result,
- * some of the response will be cached locally and only be sent when the IME is ready to show them.
- *
- * <p>
- * See {@link android.inputmethodservice.InlineSuggestionSession} comments for InputMethodService
- * side flow.
- *
- * <p>
- * This class should hold the same lock as {@link Session} as they call into each other.
- */
-final class InlineSuggestionSession {
-
-    private static final String TAG = "AfInlineSuggestionSession";
-    private static final int INLINE_REQUEST_TIMEOUT_MS = 200;
-
-    @NonNull
-    private final InputMethodManagerInternal mInputMethodManagerInternal;
-    private final int mUserId;
-    @NonNull
-    private final ComponentName mComponentName;
-    @NonNull
-    private final Object mLock;
-    @NonNull
-    private final ImeStatusListener mImeStatusListener;
-    @NonNull
-    private final Handler mHandler;
-
-    /**
-     * To avoid the race condition, one should not access {@code mPendingImeResponse} without
-     * holding the {@code mLock}. For consuming the existing value, tt's recommended to use
-     * {@link #getPendingImeResponse()} to get a copy of the reference to avoid blocking call.
-     */
-    @GuardedBy("mLock")
-    @Nullable
-    private CompletableFuture<ImeResponse> mPendingImeResponse;
-
-    @GuardedBy("mLock")
-    @Nullable
-    private AutofillResponse mPendingAutofillResponse;
-
-    @GuardedBy("mLock")
-    private boolean mIsLastResponseNonEmpty = false;
-
-    @Nullable
-    @GuardedBy("mLock")
-    private AutofillId mImeFieldId = null;
-
-    @GuardedBy("mLock")
-    private boolean mImeInputViewStarted = false;
-
-    InlineSuggestionSession(InputMethodManagerInternal inputMethodManagerInternal,
-            int userId, ComponentName componentName, Handler handler, Object lock) {
-        mInputMethodManagerInternal = inputMethodManagerInternal;
-        mUserId = userId;
-        mComponentName = componentName;
-        mHandler = handler;
-        mLock = lock;
-        mImeStatusListener = new ImeStatusListener() {
-            @Override
-            public void onInputMethodStartInput(AutofillId imeFieldId) {
-                synchronized (mLock) {
-                    mImeFieldId = imeFieldId;
-                    mImeInputViewStarted = false;
-                }
-            }
-
-            @Override
-            public void onInputMethodStartInputView() {
-                synchronized (mLock) {
-                    mImeInputViewStarted = true;
-                    AutofillResponse pendingAutofillResponse = mPendingAutofillResponse;
-                    if (pendingAutofillResponse != null
-                            && pendingAutofillResponse.mAutofillId.equalsIgnoreSession(
-                            mImeFieldId)) {
-                        mPendingAutofillResponse = null;
-                        onInlineSuggestionsResponseLocked(pendingAutofillResponse.mAutofillId,
-                                pendingAutofillResponse.mResponse);
-                    }
-                }
-            }
-
-            @Override
-            public void onInputMethodFinishInputView() {
-                synchronized (mLock) {
-                    mImeInputViewStarted = false;
-                }
-            }
-
-            @Override
-            public void onInputMethodFinishInput() {
-                synchronized (mLock) {
-                    mImeFieldId = null;
-                }
-            }
-        };
-    }
-
-    public void onCreateInlineSuggestionsRequest(@NonNull AutofillId autofillId,
-            @NonNull Consumer<InlineSuggestionsRequest> requestConsumer) {
-        if (sDebug) Log.d(TAG, "onCreateInlineSuggestionsRequest called for " + autofillId);
-
-        synchronized (mLock) {
-            // Clean up all the state about the previous request.
-            hideInlineSuggestionsUi(autofillId);
-            mImeFieldId = null;
-            mImeInputViewStarted = false;
-            if (mPendingImeResponse != null && !mPendingImeResponse.isDone()) {
-                mPendingImeResponse.complete(null);
-            }
-            mPendingImeResponse = new CompletableFuture<>();
-            // TODO(b/146454892): pipe the uiExtras from the ExtServices.
-            mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
-                    mUserId,
-                    new InlineSuggestionsRequestInfo(mComponentName, autofillId, new Bundle()),
-                    new InlineSuggestionsRequestCallbackImpl(autofillId, mPendingImeResponse,
-                            mImeStatusListener, requestConsumer, mHandler, mLock));
-        }
-    }
-
-    public Optional<InlineSuggestionsRequest> getInlineSuggestionsRequest() {
-        final CompletableFuture<ImeResponse> pendingImeResponse = getPendingImeResponse();
-        if (pendingImeResponse == null || !pendingImeResponse.isDone()) {
-            return Optional.empty();
-        }
-        return Optional.ofNullable(pendingImeResponse.getNow(null)).map(ImeResponse::getRequest);
-    }
-
-    public boolean hideInlineSuggestionsUi(@NonNull AutofillId autofillId) {
-        synchronized (mLock) {
-            if (mIsLastResponseNonEmpty) {
-                return onInlineSuggestionsResponseLocked(autofillId,
-                        new InlineSuggestionsResponse(Collections.EMPTY_LIST));
-            }
-            return false;
-        }
-    }
-
-    public boolean onInlineSuggestionsResponse(@NonNull AutofillId autofillId,
-            @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
-        synchronized (mLock) {
-            return onInlineSuggestionsResponseLocked(autofillId, inlineSuggestionsResponse);
-        }
-    }
-
-    private boolean onInlineSuggestionsResponseLocked(@NonNull AutofillId autofillId,
-            @NonNull InlineSuggestionsResponse inlineSuggestionsResponse) {
-        final CompletableFuture<ImeResponse> completedImsResponse = getPendingImeResponse();
-        if (completedImsResponse == null || !completedImsResponse.isDone()) {
-            if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked without IMS request");
-            return false;
-        }
-        // There is no need to wait on the CompletableFuture since it should have been completed.
-        ImeResponse imeResponse = completedImsResponse.getNow(null);
-        if (imeResponse == null) {
-            if (sDebug) Log.d(TAG, "onInlineSuggestionsResponseLocked with pending IMS response");
-            return false;
-        }
-
-        // TODO(b/151846600): IME doesn't have access to the virtual id of the webview, so we
-        //  only compare the view id for now.
-        if (!mImeInputViewStarted || mImeFieldId == null
-                || autofillId.getViewId() != mImeFieldId.getViewId()) {
-            if (sDebug) {
-                Log.d(TAG,
-                        "onInlineSuggestionsResponseLocked not sent because input view is not "
-                                + "started for " + autofillId);
-            }
-            mPendingAutofillResponse = new AutofillResponse(autofillId, inlineSuggestionsResponse);
-            // TODO(b/149442582): Although we are not sending the response to IME right away, we
-            //  still return true to indicate that the response may be sent eventually, such that
-            //  the dropdown UI will not be shown. This may not be the desired behavior in the
-            //  auto-focus case where IME isn't shown after switching back to an activity. We may
-            //  revisit this.
-            return true;
-        }
-
-        try {
-            imeResponse.mCallback.onInlineSuggestionsResponse(autofillId,
-                    inlineSuggestionsResponse);
-            mIsLastResponseNonEmpty = !inlineSuggestionsResponse.getInlineSuggestions().isEmpty();
-            if (sDebug) {
-                Log.d(TAG, "Autofill sends inline response to IME: "
-                        + inlineSuggestionsResponse.getInlineSuggestions().size());
-            }
-            return true;
-        } catch (RemoteException e) {
-            Slog.e(TAG, "RemoteException sending InlineSuggestionsResponse to IME");
-            return false;
-        }
-    }
-
-    @Nullable
-    @GuardedBy("mLock")
-    private CompletableFuture<ImeResponse> getPendingImeResponse() {
-        synchronized (mLock) {
-            return mPendingImeResponse;
-        }
-    }
-
-    private static final class InlineSuggestionsRequestCallbackImpl
-            extends IInlineSuggestionsRequestCallback.Stub {
-
-        private final Object mLock;
-        private final AutofillId mAutofillId;
-        @GuardedBy("mLock")
-        private final CompletableFuture<ImeResponse> mResponse;
-        @GuardedBy("mLock")
-        private final Consumer<InlineSuggestionsRequest> mRequestConsumer;
-        private final ImeStatusListener mImeStatusListener;
-        private final Handler mHandler;
-        private final Runnable mTimeoutCallback;
-
-        private InlineSuggestionsRequestCallbackImpl(AutofillId autofillId,
-                CompletableFuture<ImeResponse> response,
-                ImeStatusListener imeStatusListener,
-                Consumer<InlineSuggestionsRequest> requestConsumer,
-                Handler handler, Object lock) {
-            mAutofillId = autofillId;
-            mResponse = response;
-            mImeStatusListener = imeStatusListener;
-            mRequestConsumer = requestConsumer;
-            mLock = lock;
-
-            mHandler = handler;
-            mTimeoutCallback = () -> {
-                Log.w(TAG, "Timed out waiting for IME callback InlineSuggestionsRequest.");
-                completeIfNot(null);
-            };
-            mHandler.postDelayed(mTimeoutCallback, INLINE_REQUEST_TIMEOUT_MS);
-        }
-
-        private void completeIfNot(@Nullable ImeResponse response) {
-            synchronized (mLock) {
-                if (mResponse.isDone()) {
-                    return;
-                }
-                mResponse.complete(response);
-                mRequestConsumer.accept(response == null ? null : response.mRequest);
-                mHandler.removeCallbacks(mTimeoutCallback);
-            }
-        }
-
-        @BinderThread
-        @Override
-        public void onInlineSuggestionsUnsupported() throws RemoteException {
-            if (sDebug) Log.d(TAG, "onInlineSuggestionsUnsupported() called.");
-            completeIfNot(null);
-        }
-
-        @BinderThread
-        @Override
-        public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
-                IInlineSuggestionsResponseCallback callback) {
-            if (sDebug) Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
-            mImeStatusListener.onInputMethodStartInput(mAutofillId);
-            if (request != null && callback != null) {
-                completeIfNot(new ImeResponse(request, callback));
-            } else {
-                completeIfNot(null);
-            }
-        }
-
-        @Override
-        public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException {
-            if (sDebug) Log.d(TAG, "onInputMethodStartInput() received on " + imeFieldId);
-            mImeStatusListener.onInputMethodStartInput(imeFieldId);
-        }
-
-        @Override
-        public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException {
-            if (sDebug) {
-                Log.d(TAG, "onInputMethodShowInputRequested() received: " + requestResult);
-            }
-            // TODO(b/151123764): use this signal to adjust the timeout on Autofill side waiting for
-            //  IME to show.
-        }
-
-        @BinderThread
-        @Override
-        public void onInputMethodStartInputView() {
-            if (sDebug) Log.d(TAG, "onInputMethodStartInputView() received");
-            mImeStatusListener.onInputMethodStartInputView();
-        }
-
-        @BinderThread
-        @Override
-        public void onInputMethodFinishInputView() {
-            if (sDebug) Log.d(TAG, "onInputMethodFinishInputView() received");
-            mImeStatusListener.onInputMethodFinishInputView();
-        }
-
-        @Override
-        public void onInputMethodFinishInput() throws RemoteException {
-            if (sDebug) Log.d(TAG, "onInputMethodFinishInput() received");
-            mImeStatusListener.onInputMethodFinishInput();
-        }
-    }
-
-    private interface ImeStatusListener {
-        void onInputMethodStartInput(AutofillId imeFieldId);
-
-        void onInputMethodStartInputView();
-
-        void onInputMethodFinishInputView();
-
-        void onInputMethodFinishInput();
-    }
-
-    /**
-     * A data class wrapping Autofill responses for the inline suggestion request.
-     */
-    private static class AutofillResponse {
-        @NonNull
-        final AutofillId mAutofillId;
-
-        @NonNull
-        final InlineSuggestionsResponse mResponse;
-
-        AutofillResponse(@NonNull AutofillId autofillId,
-                @NonNull InlineSuggestionsResponse response) {
-            mAutofillId = autofillId;
-            mResponse = response;
-        }
-
-    }
-
-    /**
-     * A data class wrapping IME responses for the create inline suggestions request.
-     */
-    private static class ImeResponse {
-        @NonNull
-        final InlineSuggestionsRequest mRequest;
-
-        @NonNull
-        final IInlineSuggestionsResponseCallback mCallback;
-
-        ImeResponse(@NonNull InlineSuggestionsRequest request,
-                @NonNull IInlineSuggestionsResponseCallback callback) {
-            mRequest = request;
-            mCallback = callback;
-        }
-
-        InlineSuggestionsRequest getRequest() {
-            return mRequest;
-        }
-    }
-}
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 8b50b01..b6bc7c5 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -58,6 +58,7 @@
 import com.android.server.autofill.ui.InlineSuggestionFactory;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.TimeUnit;
@@ -263,6 +264,8 @@
                                             && fieldIds.get(0).equals(focusedId);
                                     client.autofill(sessionId, fieldIds, dataset.getFieldValues(),
                                             hideHighlight);
+                                    inlineSuggestionsCallback.apply(new InlineSuggestionsResponse(
+                                            Collections.EMPTY_LIST));
                                 } catch (RemoteException e) {
                                     Slog.w(TAG, "Encounter exception autofilling the values");
                                 }
diff --git a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
index 7ad5632..347174c 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteInlineSuggestionRenderService.java
@@ -27,7 +27,9 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.os.Bundle;
 import android.os.IBinder;
+import android.os.RemoteCallback;
 import android.service.autofill.IInlineSuggestionRenderService;
 import android.service.autofill.IInlineSuggestionUiCallback;
 import android.service.autofill.InlinePresentation;
@@ -91,6 +93,15 @@
                 hostInputToken, displayId));
     }
 
+    /**
+     * Gets the inline suggestions renderer info as a {@link Bundle}.
+     */
+    public void getInlineSuggestionsRendererInfo(@NonNull RemoteCallback callback) {
+        scheduleAsyncRequest((s) -> s.getInlineSuggestionsRendererInfo(new RemoteCallback(
+                (bundle) -> callback.sendResult(bundle)
+        )));
+    }
+
     @Nullable
     private static ServiceInfo getServiceInfo(Context context, int userId) {
         final String packageName =
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 1290569..3d68618 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -304,7 +304,7 @@
     private boolean mForAugmentedAutofillOnly;
 
     @Nullable
-    private final InlineSuggestionSession mInlineSuggestionSession;
+    private final AutofillInlineSessionController mInlineSessionController;
 
     /**
      * Receiver of assist data from the app's {@link Activity}.
@@ -652,10 +652,6 @@
         return mService.isInlineSuggestionsEnabled();
     }
 
-    private boolean isInlineSuggestionRenderServiceAvailable() {
-        return mService.getRemoteInlineSuggestionRenderServiceLocked() != null;
-    }
-
     /**
      * Clears the existing response for the partition, reads a new structure, and then requests a
      * new fill response from the fill service.
@@ -715,13 +711,18 @@
 
         // Only ask IME to create inline suggestions request if Autofill provider supports it and
         // the render service is available.
-        if (isInlineSuggestionsEnabledByAutofillProviderLocked()
-                && isInlineSuggestionRenderServiceAvailable()) {
+        final RemoteInlineSuggestionRenderService remoteRenderService =
+                mService.getRemoteInlineSuggestionRenderServiceLocked();
+        if (isInlineSuggestionsEnabledByAutofillProviderLocked() && remoteRenderService != null) {
             Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
                     mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true);
             if (inlineSuggestionsRequestConsumer != null) {
-                mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
-                        inlineSuggestionsRequestConsumer);
+                remoteRenderService.getInlineSuggestionsRendererInfo(
+                        new RemoteCallback((extras) -> {
+                            mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
+                                    mCurrentViewId, inlineSuggestionsRequestConsumer, extras);
+                        }
+                ));
             }
         } else {
             mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ false);
@@ -777,8 +778,8 @@
         mForAugmentedAutofillOnly = forAugmentedAutofillOnly;
         setClientLocked(client);
 
-        mInlineSuggestionSession = new InlineSuggestionSession(inputMethodManagerInternal, userId,
-                componentName, handler, mLock);
+        mInlineSessionController = new AutofillInlineSessionController(inputMethodManagerInternal,
+                userId, componentName, handler, mLock);
 
         mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED)
                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags));
@@ -1227,6 +1228,8 @@
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error requesting to hide fill UI", e);
             }
+
+            mInlineSessionController.hideInlineSuggestionsUiLocked(id);
         }
     }
 
@@ -2561,7 +2564,7 @@
                     if (sVerbose) Slog.v(TAG, "Exiting view " + id);
                     mUi.hideFillUi(this);
                     hideAugmentedAutofillLocked(viewState);
-                    mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId);
+                    mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
                     mCurrentViewId = null;
                 }
                 break;
@@ -2779,7 +2782,7 @@
     private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response,
             @Nullable String filterText) {
         final Optional<InlineSuggestionsRequest> inlineSuggestionsRequest =
-                mInlineSuggestionSession.getInlineSuggestionsRequest();
+                mInlineSessionController.getInlineSuggestionsRequestLocked();
         if (!inlineSuggestionsRequest.isPresent()) {
             Log.w(TAG, "InlineSuggestionsRequest unavailable");
             return false;
@@ -2801,7 +2804,8 @@
                         inlineSuggestionsRequest.get(), response, filterText, mCurrentViewId,
                         this, () -> {
                             synchronized (mLock) {
-                                mInlineSuggestionSession.hideInlineSuggestionsUi(mCurrentViewId);
+                                mInlineSessionController.hideInlineSuggestionsUiLocked(
+                                        mCurrentViewId);
                             }
                         }, remoteRenderService);
         if (inlineSuggestionsResponse == null) {
@@ -2809,7 +2813,7 @@
             return false;
         }
 
-        return mInlineSuggestionSession.onInlineSuggestionsResponse(mCurrentViewId,
+        return mInlineSessionController.onInlineSuggestionsResponseLocked(mCurrentViewId,
                 inlineSuggestionsResponse);
     }
 
@@ -3106,8 +3110,13 @@
                             focusedId,
                             currentValue, inlineSuggestionsRequest,
                             /*inlineSuggestionsCallback=*/
-                            response -> mInlineSuggestionSession.onInlineSuggestionsResponse(
-                                    mCurrentViewId, response),
+                            response -> {
+                                synchronized (mLock) {
+                                    return mInlineSessionController
+                                            .onInlineSuggestionsResponseLocked(
+                                            mCurrentViewId, response);
+                                }
+                            },
                             /*onErrorCallback=*/ () -> {
                                 synchronized (mLock) {
                                     cancelAugmentedAutofillLocked();
@@ -3121,15 +3130,21 @@
         // 1. the field is augmented autofill only (when standard autofill provider is None or
         // when it returns null response)
         // 2. standard autofill provider doesn't support inline suggestion
-        if (isInlineSuggestionRenderServiceAvailable()
+        final RemoteInlineSuggestionRenderService remoteRenderService =
+                mService.getRemoteInlineSuggestionRenderServiceLocked();
+        if (remoteRenderService != null
                 && (mForAugmentedAutofillOnly
                 || !isInlineSuggestionsEnabledByAutofillProviderLocked())) {
             if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
-            mInlineSuggestionSession.onCreateInlineSuggestionsRequest(mCurrentViewId,
-                    /*requestConsumer=*/ requestAugmentedAutofill);
+            remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback(
+                    (extras) -> {
+                        mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
+                                mCurrentViewId, /*requestConsumer=*/ requestAugmentedAutofill,
+                                extras);
+                    }, mHandler));
         } else {
             requestAugmentedAutofill.accept(
-                    mInlineSuggestionSession.getInlineSuggestionsRequest().orElse(null));
+                    mInlineSessionController.getInlineSuggestionsRequestLocked().orElse(null));
         }
         if (mAugmentedAutofillDestroyer == null) {
             mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest();
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 192ea72..3c0d880 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -119,8 +119,6 @@
     private static final int MESSAGE_DISABLE = 2;
     private static final int MESSAGE_HANDLE_ENABLE_DELAYED = 3;
     private static final int MESSAGE_HANDLE_DISABLE_DELAYED = 4;
-    private static final int MESSAGE_REGISTER_ADAPTER = 20;
-    private static final int MESSAGE_UNREGISTER_ADAPTER = 21;
     private static final int MESSAGE_REGISTER_STATE_CHANGE_CALLBACK = 30;
     private static final int MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK = 31;
     private static final int MESSAGE_BLUETOOTH_SERVICE_CONNECTED = 40;
@@ -642,10 +640,9 @@
             Slog.w(TAG, "Callback is null in registerAdapter");
             return null;
         }
-        Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_ADAPTER);
-        msg.obj = callback;
-        mHandler.sendMessage(msg);
-
+        synchronized (mCallbacks) {
+            mCallbacks.register(callback);
+        }
         return mBluetooth;
     }
 
@@ -655,9 +652,9 @@
             return;
         }
         mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
-        Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_ADAPTER);
-        msg.obj = callback;
-        mHandler.sendMessage(msg);
+        synchronized (mCallbacks) {
+            mCallbacks.unregister(callback);
+        }
     }
 
     public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) {
@@ -1559,18 +1556,20 @@
      * Inform BluetoothAdapter instances that Adapter service is up
      */
     private void sendBluetoothServiceUpCallback() {
-        try {
-            int n = mCallbacks.beginBroadcast();
-            Slog.d(TAG, "Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
-            for (int i = 0; i < n; i++) {
-                try {
-                    mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
+        synchronized (mCallbacks) {
+            try {
+                int n = mCallbacks.beginBroadcast();
+                Slog.d(TAG, "Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
+                for (int i = 0; i < n; i++) {
+                    try {
+                        mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
+                    }
                 }
+            } finally {
+                mCallbacks.finishBroadcast();
             }
-        } finally {
-            mCallbacks.finishBroadcast();
         }
     }
 
@@ -1578,18 +1577,20 @@
      * Inform BluetoothAdapter instances that Adapter service is down
      */
     private void sendBluetoothServiceDownCallback() {
-        try {
-            int n = mCallbacks.beginBroadcast();
-            Slog.d(TAG, "Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
-            for (int i = 0; i < n; i++) {
-                try {
-                    mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
+        synchronized (mCallbacks) {
+            try {
+                int n = mCallbacks.beginBroadcast();
+                Slog.d(TAG, "Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
+                for (int i = 0; i < n; i++) {
+                    try {
+                        mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
+                    }
                 }
+            } finally {
+                mCallbacks.finishBroadcast();
             }
-        } finally {
-            mCallbacks.finishBroadcast();
         }
     }
 
@@ -1917,17 +1918,6 @@
                                 mContext.getPackageName());
                     }
                     break;
-
-                case MESSAGE_REGISTER_ADAPTER: {
-                    IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj;
-                    mCallbacks.register(callback);
-                    break;
-                }
-                case MESSAGE_UNREGISTER_ADAPTER: {
-                    IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj;
-                    mCallbacks.unregister(callback);
-                    break;
-                }
                 case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK: {
                     IBluetoothStateChangeCallback callback =
                             (IBluetoothStateChangeCallback) msg.obj;
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index a9c3079..7f25de6b 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1836,12 +1836,13 @@
 
     @Override
     public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
-            PendingIntent intent, String packageName, String featureId) {
+            PendingIntent intent, String packageName, String featureId, String listenerId) {
         if (request == null) {
             request = DEFAULT_LOCATION_REQUEST;
         }
 
-        CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId);
+        CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, featureId,
+                listenerId);
         identity.enforceLocationPermission();
 
         WorkSource workSource = request.getWorkSource();
@@ -2027,7 +2028,7 @@
     @Override
     public boolean getCurrentLocation(LocationRequest locationRequest,
             ICancellationSignal remoteCancellationSignal, ILocationListener listener,
-            String packageName, String featureId) {
+            String packageName, String featureId, String listenerId) {
         // side effect of validating locationRequest and packageName
         Location lastLocation = getLastLocation(locationRequest, packageName, featureId);
         if (lastLocation != null) {
@@ -2052,7 +2053,7 @@
             }
         }
 
-        requestLocationUpdates(locationRequest, listener, null, packageName, featureId);
+        requestLocationUpdates(locationRequest, listener, null, packageName, featureId, listenerId);
         CancellationSignal cancellationSignal = CancellationSignal.fromTransport(
                 remoteCancellationSignal);
         if (cancellationSignal != null) {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0671477..b0a586d 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -220,6 +220,10 @@
 
     private static final boolean ENABLE_ISOLATED_STORAGE = StorageManager.hasIsolatedStorage();
 
+    // A system property to control if obb app data isolation is enabled in vold.
+    private static final String ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY =
+            "persist.sys.vold_app_data_isolation_enabled";
+
     /**
      * If {@code 1}, enables the isolated storage feature. If {@code -1},
      * disables the isolated storage feature. If {@code 0}, uses the default
@@ -596,6 +600,8 @@
 
     private final boolean mIsFuseEnabled;
 
+    private final boolean mVoldAppDataIsolationEnabled;
+
     @GuardedBy("mLock")
     private final Set<Integer> mUidsWithLegacyExternalStorage = new ArraySet<>();
     // Not guarded by lock, always used on the ActivityManager thread
@@ -1516,7 +1522,7 @@
         if (vol.type == VolumeInfo.TYPE_EMULATED) {
             if (newState != VolumeInfo.STATE_MOUNTED) {
                 mFuseMountedUser.remove(vol.getMountUserId());
-            } else {
+            } else if (mVoldAppDataIsolationEnabled){
                 final int userId = vol.getMountUserId();
                 mFuseMountedUser.add(userId);
                 // Async remount app storage so it won't block the main thread.
@@ -1740,6 +1746,8 @@
         // incorrect until #updateFusePropFromSettings where we set the correct value and reboot if
         // different
         mIsFuseEnabled = SystemProperties.getBoolean(PROP_FUSE, DEFAULT_FUSE_ENABLED);
+        mVoldAppDataIsolationEnabled = mIsFuseEnabled && SystemProperties.getBoolean(
+                ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
         mContext = context;
         mResolver = mContext.getContentResolver();
         mCallbacks = new Callbacks(FgThread.get().getLooper());
@@ -4468,9 +4476,8 @@
                             String.format("/storage/emulated/%d/Android/data/%s/",
                                     userId, pkg);
 
-                    int appUid =
-                            UserHandle.getUid(userId, mPmInternal.getPackage(pkg).getUid());
                     // Create package obb and data dir if it doesn't exist.
+                    int appUid = UserHandle.getUid(userId, mPmInternal.getPackage(pkg).getUid());
                     File file = new File(packageObbDir);
                     if (!file.exists()) {
                         vold.setupAppDir(packageObbDir, appUid);
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 061ff42..58972a5 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -123,6 +123,8 @@
             "android.hardware.neuralnetworks@1.0::IDevice",
             "android.hardware.power.stats@1.0::IPowerStats",
             "android.hardware.sensors@1.0::ISensors",
+            "android.hardware.sensors@2.0::ISensors",
+            "android.hardware.sensors@2.1::ISensors",
             "android.hardware.vr@1.0::IVr",
             "android.system.suspend@1.0::ISystemSuspend"
     );
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index bc45533..8f8990f 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -98,7 +98,7 @@
                 final long endTime = SystemClock.uptimeMillis();
                 Slog.d(TAG, "Completed ANR of " + r.mApp.processName + " in "
                         + (endTime - startTime) + "ms, latency " + reportLatency
-                        + (onlyDumpSelf ? "ms" : "ms (expired, only dump ANR app)"));
+                        + (onlyDumpSelf ? "ms (expired, only dump ANR app)" : "ms"));
             }
 
             mRunning.set(false);
diff --git a/services/core/java/com/android/server/am/BugReportHandlerUtil.java b/services/core/java/com/android/server/am/BugReportHandlerUtil.java
index ba89fce..0a0d8d8 100644
--- a/services/core/java/com/android/server/am/BugReportHandlerUtil.java
+++ b/services/core/java/com/android/server/am/BugReportHandlerUtil.java
@@ -16,15 +16,20 @@
 
 package com.android.server.am;
 
+import static android.app.AppOpsManager.OP_NONE;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
+import android.app.Activity;
 import android.app.BroadcastOptions;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
+import android.os.BugreportManager;
+import android.os.BugreportParams;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -43,6 +48,8 @@
     private static final String SHELL_APP_PACKAGE = "com.android.shell";
     private static final String INTENT_BUGREPORT_REQUESTED =
             "com.android.internal.intent.action.BUGREPORT_REQUESTED";
+    private static final String INTENT_GET_BUGREPORT_HANDLER_RESPONSE =
+            "com.android.internal.intent.action.GET_BUGREPORT_HANDLER_RESPONSE";
 
     /**
      * Check is BugReportHandler enabled on the device.
@@ -100,6 +107,43 @@
             return false;
         }
 
+        if (getBugReportHandlerAppResponseReceivers(context, handlerApp, handlerUser).isEmpty()) {
+            // Just try to launch bugreport handler app to handle bugreport request
+            // because the bugreport handler app is old and not support to provide response to
+            // let BugReportHandlerUtil know it is available or not.
+            launchBugReportHandlerApp(context, handlerApp, handlerUser);
+            return true;
+        }
+
+        Slog.i(TAG, "Getting response from bug report handler app: " + handlerApp);
+        Intent intent = new Intent(INTENT_GET_BUGREPORT_HANDLER_RESPONSE);
+        intent.setPackage(handlerApp);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            // Handler app's BroadcastReceiver should call setResultCode(Activity.RESULT_OK) to
+            // let BugreportHandlerResponseBroadcastReceiver know the handler app is available.
+            context.sendOrderedBroadcastAsUser(intent,
+                    UserHandle.of(handlerUser),
+                    android.Manifest.permission.DUMP,
+                    OP_NONE, /* options= */ null,
+                    new BugreportHandlerResponseBroadcastReceiver(handlerApp, handlerUser),
+                    /* scheduler= */ null,
+                    Activity.RESULT_CANCELED,
+                    /* initialData= */ null,
+                    /* initialExtras= */ null);
+        } catch (RuntimeException e) {
+            Slog.e(TAG, "Error while trying to get response from bug report handler app.", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        return true;
+    }
+
+    private static void launchBugReportHandlerApp(Context context, String handlerApp,
+            int handlerUser) {
         Slog.i(TAG, "Launching bug report handler app: " + handlerApp);
         Intent intent = new Intent(INTENT_BUGREPORT_REQUESTED);
         intent.setPackage(handlerApp);
@@ -115,11 +159,9 @@
                     options.toBundle());
         } catch (RuntimeException e) {
             Slog.e(TAG, "Error while trying to launch bugreport handler app.", e);
-            return false;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
-        return true;
     }
 
     private static String getCustomBugReportHandlerApp(Context context) {
@@ -159,6 +201,16 @@
                         handlerUser);
     }
 
+    private static List<ResolveInfo> getBugReportHandlerAppResponseReceivers(Context context,
+            String handlerApp, int handlerUser) {
+        // Use the app package and the user id to retrieve the receiver that can provide response
+        Intent intent = new Intent(INTENT_GET_BUGREPORT_HANDLER_RESPONSE);
+        intent.setPackage(handlerApp);
+        return context.getPackageManager()
+                .queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_SYSTEM_ONLY,
+                        handlerUser);
+    }
+
     private static String getDefaultBugReportHandlerApp(Context context) {
         return context.getResources().getString(
                 com.android.internal.R.string.config_defaultBugReportHandlerApp);
@@ -176,4 +228,30 @@
             Binder.restoreCallingIdentity(identity);
         }
     }
+
+    private static class BugreportHandlerResponseBroadcastReceiver extends BroadcastReceiver {
+        private final String handlerApp;
+        private final int handlerUser;
+
+        BugreportHandlerResponseBroadcastReceiver(String handlerApp, int handlerUser) {
+            this.handlerApp = handlerApp;
+            this.handlerUser = handlerUser;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (getResultCode() == Activity.RESULT_OK) {
+                // Try to launch bugreport handler app to handle bugreport request because the
+                // bugreport handler app is available.
+                launchBugReportHandlerApp(context, handlerApp, handlerUser);
+                return;
+            }
+
+            Slog.w(TAG, "Request bug report because no response from handler app.");
+            BugreportManager bugreportManager = context.getSystemService(BugreportManager.class);
+            bugreportManager.requestBugreport(
+                    new BugreportParams(BugreportParams.BUGREPORT_MODE_INTERACTIVE),
+                    /* shareTitle= */null, /* shareDescription= */ null);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index dbcb3da..2d6ef81 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -231,7 +231,8 @@
         final ServiceThread adjusterThread =
                 new ServiceThread(TAG, TOP_APP_PRIORITY_BOOST, false /* allowIo */);
         adjusterThread.start();
-        Process.setThreadGroupAndCpuset(adjusterThread.getThreadId(), THREAD_GROUP_TOP_APP);
+        adjusterThread.getThreadHandler().post(() -> Process.setThreadGroupAndCpuset(
+                adjusterThread.getThreadId(), THREAD_GROUP_TOP_APP));
         return adjusterThread;
     }
 
diff --git a/services/core/java/com/android/server/am/OomAdjuster.md b/services/core/java/com/android/server/am/OomAdjuster.md
new file mode 100644
index 0000000..eda511a
--- /dev/null
+++ b/services/core/java/com/android/server/am/OomAdjuster.md
@@ -0,0 +1,129 @@
+<!-- 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.
+-->
+
+# Oom Adjuster Designs
+
+## Purpose of Oom Adjuster
+
+The Android OS runs with limited hardware resources, i.e. CPU/RAM/Power. To strive for the better performance, Oom Ajuster is introduced to tweak the following 3 major factors:
+
+ * Process State
+   * Wildly used by the System Server, i.e., determine if it's foreground or not, change the GC behavior, etc.
+   * Defined in `ActivityManager#PROCESS_STATE_*`
+ * Oom Adj score
+   * Used by the lmkd to determine which process should be expunged on memory pressure.
+   * Defined in `ProcessList#*_ADJ`
+ * Scheduler Group
+   * Used to tweak the process group, thread priorities.
+   * Top process is scheduled to be running on a dedicated big core, while foreground processes take the other big cores; background processes stay with LITTLE cores instead.
+
+## Process Capabilities
+
+Besides the above 3 major factors, Android R introduced the Process Capabilities `ActivityManager#PROCESS_CAPABILITY_*`.  It's a new attribute to process record, mainly designed for supporting the "while-in-use" permission model - in additional to the traditional Android permissions, wheather or not a process has access to a given API, will be guarded by its current process state as well. The OomAdjuster will compute the process capabilities during updating the oom adj. Meanwhile, the flag `ActivityManager#BIND_INCLUDE_CAPABILITIES` enables to possiblity to "transfer" the capability from a client process to the service process it binds to.
+
+## Rationale of Oom Adjuster
+
+System server keeps a list of recent used app processes. Given the 4 types of entities that an Android processes could have: Activity, Service, Content Provider and Broadcast Receiver, the System Server has to adjust the above 3 factors to give the users the best performance according to the states of the entities. A typical case would be that: foreground app A binds into a background service B in order to serve the user, in the case of memory pressure, the background service B should be avoided from being expunged since it would result user-perceptible interruption of service. The Oom Adjuster is to tweak the aforementioned 3 factors for those app processes.
+
+The timing of updating the Oom Adj score is vital: assume a camera process in background gets launched into foreground, launching camera typically incurs high memory pressure, which could incur low memory kills - if the camera process isn't moved out of the background adj group, it could get killed by lmkd. Therefore the updates have to be called pretty frequently: in case there is an activity start, service binding, etc.
+
+The update procedure basically consists of 3 parts:
+  * Find out the process record to be updated
+    * There are two categories of updateOomAdjLocked: one with the target process record to be updated, while the other one is to update all process record.
+    * Besides that, while computing the Oom Aj score, the clients of service connections or content providers of the present process record, which forms a process dependency graph actually, will be evaluated as well.
+    * Starting from Android R, when updating for a specific process record, an optimization is made that, only the reachable process records starting from this process record in the process dependency graph, will be re-evaluated.
+    * The `cached` Oom Adj scores are grouped in `bucket`, which is used in the isolated processes: they could be correlated - assume one isolated Chrome process is at Oom Adj score 920 and another one is 980; the later one could get expunged much earlier than the former one, which doesn't make sense; grouping them would be a big relief for this case.
+  * Compute Oom Adj score
+    * This procedure returns true if there is a score change, false if there is no.
+    * The curAdj field in the process record is used as an intermediate value during the computation.
+    * Initialize the Process State to `PROCESS_STATE_CACHED_EMPTY`, which is the lowest importance.
+    * Calculate the scores based on various factors:
+      * If it's not allowed to be lower than `ProcessList#FOREGROUND_APP_ADJ`, meaning it's propbably a persistent process, there is no too much to do here.
+      * Exame if the process is the top app, running remote animation, running instrumentation, receiving broadcast, executing services, running on top but sleeping (screen off), update the intermediate values.
+      * Ask Window Manager (yes, ActivityTaskManager is with WindowManager now) to tell each activity's visibility information.
+      * Check if the process has recent tasks, check if it's hosting a foreground service, overlay UI, toast etc. Note for the foreground service, if it was in foreground status, allow it to stay in higher rank in memory for a while: Assuming a camera captureing case, where the camera app is still processing the picture while being switched out of foreground - keep it stay in higher rank in memory would ensure the pictures are persisted correctly.
+      * Check if the process is the heavy weight process, whose launching/exiting would be slow and it's better to keep it in the memory. Note there should be only one heavy weight process across the system.
+      * For sure the Home process shouldn't be expunged frequently as well.
+      * The next two factors are either it was the previous process with visible UI to the user, or it's a backup agent.
+      * And then it goes to the massive searches against the service connections and the content providers, each of the clients will be evaluated, and the Oom Adj score could get updated according to its clients' scores. However there are a bunch of service binding flags which could impact the result:
+        * Below table captures the results with given various service binding states:
+        | Conditon #1                     | Condition #2                                               | Condition #3                                 | Condition #4                                      | Result                   |
+        |---------------------------------|------------------------------------------------------------|----------------------------------------------|---------------------------------------------------|--------------------------|
+        | `BIND_WAIVE_PRIORITY` not set   | `BIND_ALLOW_OOM_MANAGEMENT` set                            | Shown UI && Not Home                         |                                                   | Use the app's own Adj    |
+        |                                 |                                                            | Inactive for a while                         |                                                   | Use the app's own Adj    |
+        |                                 | Client has a higher importance                             | Shown UI && Not Home && client is invisible  |                                                   | Use the app's own Adj    |
+        |                                 |                                                            | `BIND_ABOVE_CLIENT` and `BIND_IMPORTANT` set | Client is not persistent                          | Try client's Adj         |
+        |                                 |                                                            |                                              | Client is persistent                              | Try persistent Adj       |
+        |                                 |                                                            | `BIND_NOT_PERCEPTIBLE` set                   | client < perceptible && app > low perceptible     | Try low perceptible Adj  |
+        |                                 |                                                            | `BIND_NOT_VISIBLE` set                       | client < perceptible && app > perceptible         | Try perceptible Adj      |
+        |                                 |                                                            | Client >= perceptible                        |                                                   | Try client's Adj         |
+        |                                 |                                                            | Adj > visible                                |                                                   | Max of client/Own Adj    |
+        |                                 |                                                            |                                              |                                                   | Use the app's own Adj    |
+        |                                 | `BIND_NOT_FOREGROUND`+`BIND_IMPORTANT_BACKGROUND` not set  | Client's sched group > app's                 | `BIND_IMPORTANT` is set                           | Use client's sched group |
+        |                                 |                                                            |                                              |                                                   | Use default sched group  |
+        |                                 |                                                            | Client's process state < top                 | `BIND_FOREGROUND_SERVICE` is set                  | ProcState = bound fg     |
+        |                                 |                                                            |                                              | `BIND_FOREGROUND_SERVICE_WHILE_AWAKE` + screen ON | ProcState = bound fg     |
+        |                                 |                                                            |                                              |                                                   | ProcState = important fg |
+        |                                 |                                                            | Client's process state = top                 |                                                   | ProcState = bound top    |
+        |                                 | `BIND_IMPORTANT_BACKGROUND` not set                        | Client's process state < transient bg        |                                                   | ProcState = transient bg |
+        |                                 | `BIND_NOT_FOREGROUND` or `BIND_IMPORTANT_BACKGROUND` set   | Client's process state < important bg        |                                                   | ProcState = important bg |
+        | `BIND_ADJUST_WITH_ACTIVITY` set | Adj > fg && App visible                                    |                                              |                                                   | Adj = foreground         |
+        |                                 |                                                            | `BIND_NOT_FOREGROUND` not set                | `BIND_IMPORTANT` is set                           | Sched = top app bound    |
+        |                                 |                                                            |                                              | `BIND_IMPORTANT` is NOT set                       | Sched = default          |
+        * Below table captures the results with given various content provider binding states:
+        | Conditon #1                     | Condition #2                                               | Condition #3                                 | Result                   |
+        |---------------------------------|------------------------------------------------------------|----------------------------------------------|--------------------------|
+        | Client's process state >= cached|                                                            |                                              | Client ProcState = empty |
+        | Adj > Client Adj                | Not shown UI or is Home, or Client's Adj <= perceptible    | Client's Adj <= foreground Adj               | Try foreground Adj       |
+        |                                 |                                                            | Client's Adj > foreground Adj                | Try client's Adj         |
+        | Client's process state <= fg svc| Client's process state is top                              |                                              | ProcState = bound top    |
+        |                                 | Client's process state is NOT top                          |                                              | ProcState = bound fg svc |
+        | Has external dependencies       | Adj > fg app                                               |                                              | adj = fg app             |
+        |                                 | Process state > important foreground                       |                                              | ProcState = important fg |
+        | Still within retain time        | Adj > previous app Adj                                     |                                              | adj = previuos app adj   |
+        |                                 | Process state > last activity                              |                                              | ProcState = last activity|
+        * Some additional tweaks after the above ones:
+        | Conditon #1                     | Condition #2                                               | Condition #3                                 | Result                             |
+        |---------------------------------|------------------------------------------------------------|----------------------------------------------|------------------------------------|
+        | Process state >= cached empty   | Has client activities                                      |                                              | ProcState = cached activity client |
+        |                                 | treat like activity (IME)                                  |                                              | ProcState = cached activity        |
+        | Adj is service adj              | computing all process records                              | Num of new service A > 1/3 of services       | Push it to service B               |
+        |                                 |                                                            | Low on RAM and app process's PSS is large    | Push it to service B               |
+  * Apply the scores, which consists of: write into kernel sysfs entries to update the Oom Adj scores; call kernel API to set the thread priorities, and then tell the world the new process state
+
+## Cycles, Cycles, Cycles
+
+Another interesting aspect of the Oom Adjuster is the cycles of the dependencies. A simple example would be like below illustration, process A is hosting a service which is bound by process B; meanwhile the process B is hosting a service which is bound by process A.
+<pre>
+  +-------------+           +-------------+
+  |  Process A  | <-------- |  Process B  |
+  | (service 1) | --------> | (service 2) |
+  +-------------+           +-------------+
+</pre>
+
+There could be very complicated cases, which could involve multiple cycles, and in the dependency graph, each of the process record node could have different importance.
+<pre>
+  +-------------+           +-------------+           +-------------+           +-------------+           +-------------+
+  |  Process D  | --------> |  Process A  | <-------- |  Process B  | <-------- |  Process C  | <-------- |  Process A  |
+  |             |           | (service 1) |           | (service 2) |           | (service 3) |           | (service 1) |
+  +-------------+           +-------------+           +-------------+           +-------------+           +-------------+
+</pre>
+
+The Oom Adjuster maintains a global sequence ID `mAdjSeq` to track the current Oom Adjuster calling. And each of the process record has a field to track in which sequence the process record is evaluated. If during the Oom Adj computation, a process record with sequence ID as same as the current global sequence ID, this would mean that a cycle is detected; in this case:
+  * Decrement the sequence ID of each process if there is a cycle.
+  * Re-evaluate each of the process record within the cycle until nothing was promoted.
+  * Iterate the processes from least important to most important ones.
+  * A maximum retries of 10 is enforced, while in practice, the maximum retries could reach only 2 to 3.
+
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 595275d..89fa02b 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -97,6 +97,7 @@
 import android.system.Os;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.LongSparseArray;
 import android.util.Pair;
@@ -137,6 +138,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Activity manager code dealing with processes.
@@ -152,6 +154,9 @@
     static final String ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY =
             "persist.sys.vold_app_data_isolation_enabled";
 
+    // A system property to control if fuse is enabled.
+    static final String ANDROID_FUSE_ENABLED = "persist.sys.fuse";
+
     // The minimum time we allow between crashes, for us to consider this
     // application to be bad and stop and its services and reject broadcasts.
     static final int MIN_CRASH_INTERVAL = 60 * 1000;
@@ -705,8 +710,13 @@
         // want some apps enabled while some apps disabled
         mAppDataIsolationEnabled =
                 SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
-        mVoldAppDataIsolationEnabled = SystemProperties.getBoolean(
+        boolean fuseEnabled = SystemProperties.getBoolean(ANDROID_FUSE_ENABLED, false);
+        boolean voldAppDataIsolationEnabled = SystemProperties.getBoolean(
                 ANDROID_VOLD_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+        if (!fuseEnabled && voldAppDataIsolationEnabled) {
+            Slog.e(TAG, "Fuse is not enabled while vold app data isolation is enabled");
+        }
+        mVoldAppDataIsolationEnabled = fuseEnabled && voldAppDataIsolationEnabled;
         mAppDataIsolationWhitelistedApps = new ArrayList<>(
                 SystemConfig.getInstance().getAppDataIsolationWhitelistedApps());
 
@@ -1846,11 +1856,13 @@
                 runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE;
             }
 
-            // Enable heap pointer tagging, unless disabled by the app manifest, target sdk level,
-            // or the compat feature.
-            if (app.info.allowsNativeHeapPointerTagging()
-                    && mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
-                runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+            if (Zygote.nativeSupportsTaggedPointers()) {
+                // Enable heap pointer tagging if supported by the kernel, unless disabled by the
+                // app manifest, target sdk level, or compat feature.
+                if (app.info.allowsNativeHeapPointerTagging()
+                        && mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
+                    runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+                }
             }
 
             runtimeFlags |= decideGwpAsanLevel(app);
@@ -2127,18 +2139,11 @@
         for (String packageName : packages) {
             String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid();
             long inode = pmInt.getCeDataInode(packageName, userId);
-            if (inode != 0) {
-                result.put(packageName, Pair.create(volumeUuid, inode));
+            if (inode == 0) {
+                Slog.w(TAG, packageName + " inode == 0 (b/152760674)");
+                return null;
             }
-        }
-        if (mAppDataIsolationWhitelistedApps != null) {
-            for (String packageName : mAppDataIsolationWhitelistedApps) {
-                String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid();
-                long inode = pmInt.getCeDataInode(packageName, userId);
-                if (inode != 0) {
-                    result.put(packageName, Pair.create(volumeUuid, inode));
-                }
-            }
+            result.put(packageName, Pair.create(volumeUuid, inode));
         }
 
         return result;
@@ -2160,35 +2165,56 @@
                 app.setHasForegroundActivities(true);
             }
 
+            final Map<String, Pair<String, Long>> pkgDataInfoMap;
+            final Map<String, Pair<String, Long>> whitelistedAppDataInfoMap;
+            boolean bindMountAppStorageDirs = false;
+            boolean bindMountAppsData = mAppDataIsolationEnabled
+                    && UserHandle.isApp(app.uid)
+                    && mPlatformCompat.isChangeEnabled(APP_DATA_DIRECTORY_ISOLATION, app.info);
+
+            // Get all packages belongs to the same shared uid. sharedPackages is empty array
+            // if it doesn't have shared uid.
+            final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked();
+            final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage(
+                    app.info.packageName, app.userId);
+            final String[] targetPackagesList = sharedPackages.length == 0
+                    ? new String[]{app.info.packageName} : sharedPackages;
+
+            pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, targetPackagesList, uid);
+            if (pkgDataInfoMap == null) {
+                // TODO(b/152760674): Handle inode == 0 case properly, now we just give it a
+                // tmp free pass.
+                bindMountAppsData = false;
+            }
+
+            // Remove all packages in pkgDataInfoMap from mAppDataIsolationWhitelistedApps, so
+            // it won't be mounted twice.
+            final Set<String> whitelistedApps = new ArraySet<>(mAppDataIsolationWhitelistedApps);
+            for (String pkg : targetPackagesList) {
+                whitelistedApps.remove(pkg);
+            }
+
+            whitelistedAppDataInfoMap = getPackageAppDataInfoMap(pmInt,
+                    whitelistedApps.toArray(new String[0]), uid);
+            if (whitelistedAppDataInfoMap == null) {
+                // TODO(b/152760674): Handle inode == 0 case properly, now we just give it a
+                // tmp free pass.
+                bindMountAppsData = false;
+            }
+
+            int userId = UserHandle.getUserId(uid);
             StorageManagerInternal storageManagerInternal = LocalServices.getService(
                     StorageManagerInternal.class);
-            final Map<String, Pair<String, Long>> pkgDataInfoMap;
-            boolean bindMountAppStorageDirs = false;
-
-            if (mAppDataIsolationEnabled && UserHandle.isApp(app.uid)
-                    && mPlatformCompat.isChangeEnabled(APP_DATA_DIRECTORY_ISOLATION, app.info)) {
-                // Get all packages belongs to the same shared uid. sharedPackages is empty array
-                // if it doesn't have shared uid.
-                final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked();
-                final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage(
-                        app.info.packageName, app.userId);
-                pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, sharedPackages.length == 0
-                        ? new String[]{app.info.packageName} : sharedPackages, uid);
-
-                int userId = UserHandle.getUserId(uid);
-                if (mVoldAppDataIsolationEnabled
-                        && !storageManagerInternal.isExternalStorageService(uid)) {
-                    bindMountAppStorageDirs = true;
-                    if (!storageManagerInternal.prepareStorageDirs(userId, pkgDataInfoMap.keySet(),
-                            app.processName)) {
-                        // Cannot prepare Android/app and Android/obb directory,
-                        // so we won't mount it in zygote.
-                        app.bindMountPending = true;
-                        bindMountAppStorageDirs = false;
-                    }
+            if (mVoldAppDataIsolationEnabled && UserHandle.isApp(app.uid)
+                    && !storageManagerInternal.isExternalStorageService(uid)) {
+                bindMountAppStorageDirs = true;
+                if (!storageManagerInternal.prepareStorageDirs(userId, pkgDataInfoMap.keySet(),
+                        app.processName)) {
+                    // Cannot prepare Android/app and Android/obb directory,
+                    // so we won't mount it in zygote.
+                    app.bindMountPending = true;
+                    bindMountAppStorageDirs = false;
                 }
-            } else {
-                pkgDataInfoMap = null;
             }
 
             final Process.ProcessStartResult startResult;
@@ -2206,7 +2232,8 @@
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, null, app.info.packageName,
                         /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
-                        app.mDisabledCompatChanges, pkgDataInfoMap, bindMountAppStorageDirs,
+                        app.mDisabledCompatChanges, pkgDataInfoMap, whitelistedAppDataInfoMap,
+                        bindMountAppsData, bindMountAppStorageDirs,
                         new String[]{PROC_START_SEQ_IDENT + app.startSeq});
             } else {
                 startResult = Process.start(entryPoint,
@@ -2214,7 +2241,7 @@
                         app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                         app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags,
                         isTopApp, app.mDisabledCompatChanges, pkgDataInfoMap,
-                        bindMountAppStorageDirs,
+                        whitelistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs,
                         new String[]{PROC_START_SEQ_IDENT + app.startSeq});
             }
             checkSlow(startTime, "startProcess: returned from zygote!");
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 30e765f..02d8571 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3622,7 +3622,15 @@
                 hdlr = h;
                 // Remove from client list so that it is re-inserted at top of list
                 iter.remove();
-                hdlr.getBinder().unlinkToDeath(hdlr, 0);
+                try {
+                    hdlr.getBinder().unlinkToDeath(hdlr, 0);
+                    if (cb != hdlr.getBinder()) {
+                        hdlr = null;
+                    }
+                } catch (NoSuchElementException e) {
+                    hdlr = null;
+                    Log.w(TAG, "link does not exist ...");
+                }
                 break;
             }
         }
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index 4431abe..808f8c21 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -558,8 +558,10 @@
             FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
                     statsModality(), BiometricsProtoEnums.ISSUE_CANCEL_TIMED_OUT);
 
+            ClientMonitor newClient = mPendingClient;
             mCurrentClient = null;
-            startClient(mPendingClient, false);
+            mPendingClient = null;
+            startClient(newClient, false);
         }
     }
 
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 962f337..9a910bf 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -569,9 +569,10 @@
                 // Immediately dispatch notifications to foreground apps that
                 // are important to the user; all other background observers are
                 // delayed to avoid stampeding
+                final boolean noDelay = (key.flags & ContentResolver.NOTIFY_NO_DELAY) != 0;
                 final int procState = LocalServices.getService(ActivityManagerInternal.class)
                         .getUidProcessState(key.uid);
-                if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND || noDelay) {
                     task.run();
                 } else {
                     BackgroundThread.getHandler().postDelayed(task, BACKGROUND_OBSERVER_DELAY);
diff --git a/services/core/java/com/android/server/display/color/AppSaturationController.java b/services/core/java/com/android/server/display/color/AppSaturationController.java
index e42be02..6a685bf 100644
--- a/services/core/java/com/android/server/display/color/AppSaturationController.java
+++ b/services/core/java/com/android/server/display/color/AppSaturationController.java
@@ -17,6 +17,7 @@
 package com.android.server.display.color;
 
 import android.annotation.UserIdInt;
+import android.util.ArrayMap;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -61,11 +62,12 @@
      * Set the saturation level ({@code ColorDisplayManager#SaturationLevel} constant for a given
      * package name and userId.
      */
-    public boolean setSaturationLevel(String packageName, @UserIdInt int userId,
+    public boolean setSaturationLevel(String callingPackageName, String affectedPackageName,
+            @UserIdInt int userId,
             int saturationLevel) {
         synchronized (mLock) {
-            return getSaturationControllerLocked(packageName, userId)
-                    .setSaturationLevel(saturationLevel);
+            return getSaturationControllerLocked(affectedPackageName, userId)
+                    .setSaturationLevel(callingPackageName, saturationLevel);
         }
     }
 
@@ -148,13 +150,19 @@
 
     private static class SaturationController {
 
+        private static final int FULL_SATURATION = 100;
+
         private final List<WeakReference<ColorTransformController>> mControllerRefs =
                 new ArrayList<>();
-        private int mSaturationLevel = 100;
+        private final ArrayMap<String, Integer> mSaturationLevels = new ArrayMap<>();
         private float[] mTransformMatrix = new float[9];
 
-        private boolean setSaturationLevel(int saturationLevel) {
-            mSaturationLevel = saturationLevel;
+        private boolean setSaturationLevel(String callingPackageName, int saturationLevel) {
+            if (saturationLevel == FULL_SATURATION) {
+                mSaturationLevels.remove(callingPackageName);
+            } else {
+                mSaturationLevels.put(callingPackageName, saturationLevel);
+            }
             if (!mControllerRefs.isEmpty()) {
                 return updateState();
             }
@@ -163,17 +171,27 @@
 
         private boolean addColorTransformController(
                 WeakReference<ColorTransformController> controller) {
+            clearExpiredReferences();
             mControllerRefs.add(controller);
-            if (mSaturationLevel != 100) {
+            if (!mSaturationLevels.isEmpty()) {
                 return updateState();
-            } else {
-                clearExpiredReferences();
             }
             return false;
         }
 
+        private int calculateSaturationLevel() {
+            int saturationLevel = FULL_SATURATION;
+            for (int i = 0; i < mSaturationLevels.size(); i++) {
+                final int level = mSaturationLevels.valueAt(i);
+                if (level < saturationLevel) {
+                    saturationLevel = level;
+                }
+            }
+            return saturationLevel;
+        }
+
         private boolean updateState() {
-            computeGrayscaleTransformMatrix(mSaturationLevel / 100f, mTransformMatrix);
+            computeGrayscaleTransformMatrix(calculateSaturationLevel() / 100f, mTransformMatrix);
 
             boolean updated = false;
             final Iterator<WeakReference<ColorTransformController>> iterator = mControllerRefs
@@ -190,7 +208,6 @@
                 }
             }
             return updated;
-
         }
 
         private void clearExpiredReferences() {
@@ -206,7 +223,7 @@
         }
 
         private void dump(PrintWriter pw) {
-            pw.println("            mSaturationLevel: " + mSaturationLevel);
+            pw.println("            mSaturationLevels: " + mSaturationLevels);
             pw.println("            mControllerRefs count: " + mControllerRefs.size());
         }
     }
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 2dc2cf0..95a98f1 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -44,6 +44,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.display.ColorDisplayManager;
@@ -73,6 +74,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.server.DisplayThread;
+import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.twilight.TwilightListener;
 import com.android.server.twilight.TwilightManager;
@@ -817,9 +819,11 @@
         return LocalDateTime.MIN;
     }
 
-    private boolean setAppSaturationLevelInternal(String packageName, int saturationLevel) {
+    private boolean setAppSaturationLevelInternal(String callingPackageName,
+            String affectedPackageName, int saturationLevel) {
         return mAppSaturationController
-                .setSaturationLevel(packageName, mCurrentUser, saturationLevel);
+                .setSaturationLevel(callingPackageName, affectedPackageName, mCurrentUser,
+                        saturationLevel);
     }
 
     private void setColorModeInternal(@ColorMode int colorMode) {
@@ -1533,9 +1537,11 @@
             getContext().enforceCallingPermission(
                     Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS,
                     "Permission required to set display saturation level");
+            final String callingPackageName = LocalServices.getService(PackageManagerInternal.class)
+                    .getNameForUid(Binder.getCallingUid());
             final long token = Binder.clearCallingIdentity();
             try {
-                return setAppSaturationLevelInternal(packageName, level);
+                return setAppSaturationLevelInternal(callingPackageName, packageName, level);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/services/core/java/com/android/server/location/AppOpsHelper.java b/services/core/java/com/android/server/location/AppOpsHelper.java
index 9c27916..cb64c50 100644
--- a/services/core/java/com/android/server/location/AppOpsHelper.java
+++ b/services/core/java/com/android/server/location/AppOpsHelper.java
@@ -191,7 +191,7 @@
                     callerIdentity.uid,
                     callerIdentity.packageName,
                     callerIdentity.featureId,
-                    null) == AppOpsManager.MODE_ALLOWED;
+                    callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -210,7 +210,7 @@
                     callerIdentity.packageName,
                     false,
                     callerIdentity.featureId,
-                    null) == AppOpsManager.MODE_ALLOWED;
+                    callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -245,7 +245,7 @@
                     callerIdentity.uid,
                     callerIdentity.packageName,
                     callerIdentity.featureId,
-                    null) == AppOpsManager.MODE_ALLOWED;
+                    callerIdentity.listenerId) == AppOpsManager.MODE_ALLOWED;
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
diff --git a/services/core/java/com/android/server/location/CallerIdentity.java b/services/core/java/com/android/server/location/CallerIdentity.java
index b84fd13..8d508bb 100644
--- a/services/core/java/com/android/server/location/CallerIdentity.java
+++ b/services/core/java/com/android/server/location/CallerIdentity.java
@@ -83,12 +83,22 @@
      */
     public static CallerIdentity fromBinder(Context context, String packageName,
             @Nullable String featureId) {
+        return fromBinder(context, packageName, featureId, null);
+    }
+
+    /**
+     * Creates a CallerIdentity from the current binder identity, using the given package, feature
+     * id, and listener id. The package will be checked to enforce it belongs to the calling uid,
+     * and a security exception will be thrown if it is invalid.
+     */
+    public static CallerIdentity fromBinder(Context context, String packageName,
+            @Nullable String featureId, @Nullable String listenerId) {
         int uid = Binder.getCallingUid();
         if (!ArrayUtils.contains(context.getPackageManager().getPackagesForUid(uid), packageName)) {
             throw new SecurityException("invalid package \"" + packageName + "\" for uid " + uid);
         }
 
-        return fromBinderUnsafe(context, packageName, featureId);
+        return fromBinderUnsafe(context, packageName, featureId, listenerId);
     }
 
     /**
@@ -99,8 +109,19 @@
      */
     public static CallerIdentity fromBinderUnsafe(Context context, String packageName,
             @Nullable String featureId) {
+        return fromBinderUnsafe(context, packageName, featureId, null);
+    }
+
+    /**
+     * Creates a CallerIdentity from the current binder identity, using the given package, feature
+     * id, and listener id. The package will not be checked to enforce that it belongs to the
+     * calling uid - this method should only be used if the package will be validated by some other
+     * means, such as an appops call.
+     */
+    public static CallerIdentity fromBinderUnsafe(Context context, String packageName,
+            @Nullable String featureId, @Nullable String listenerId) {
         return new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(),
-                UserHandle.getCallingUserId(), packageName, featureId,
+                UserHandle.getCallingUserId(), packageName, featureId, listenerId,
                 getBinderPermissionLevel(context));
     }
 
@@ -157,6 +178,9 @@
     /** The calling feature id. */
     public final @Nullable String featureId;
 
+    /** The calling listener id. */
+    public final @Nullable String listenerId;
+
     /**
      * The calling location permission level. This field should only be used for validating
      * permissions for API access. It should not be used for validating permissions for location
@@ -167,11 +191,18 @@
     @VisibleForTesting
     public CallerIdentity(int uid, int pid, int userId, String packageName,
             @Nullable String featureId, @PermissionLevel int permissionLevel) {
+        this(uid, pid, userId, packageName, featureId, null, permissionLevel);
+    }
+
+    private CallerIdentity(int uid, int pid, int userId, String packageName,
+            @Nullable String featureId, @Nullable String listenerId,
+            @PermissionLevel int permissionLevel) {
         this.uid = uid;
         this.pid = pid;
         this.userId = userId;
         this.packageName = Objects.requireNonNull(packageName);
         this.featureId = featureId;
+        this.listenerId = listenerId;
         this.permissionLevel = Preconditions.checkArgumentInRange(permissionLevel, PERMISSION_NONE,
                 PERMISSION_FINE, "permissionLevel");
     }
@@ -216,7 +247,8 @@
         return uid == that.uid
                 && pid == that.pid
                 && packageName.equals(that.packageName)
-                && Objects.equals(featureId, that.featureId);
+                && Objects.equals(featureId, that.featureId)
+                && Objects.equals(listenerId, that.listenerId);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index ad3c8a6..d8acf0e 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -809,11 +809,11 @@
         locationRequest.setProvider(provider);
 
         // Ignore location settings if in emergency mode. This is only allowed for
-        // isUserEmergency request (introduced in HAL v2.0), or DBH request in HAL v1.1.
+        // isUserEmergency request (introduced in HAL v2.0), or HAL v1.1.
         if (mNIHandler.getInEmergency()) {
             GnssConfiguration.HalInterfaceVersion halVersion =
                     mGnssConfiguration.getHalInterfaceVersion();
-            if (isUserEmergency || (halVersion.mMajor < 2 && !independentFromGnss)) {
+            if (isUserEmergency || halVersion.mMajor < 2) {
                 locationRequest.setLocationSettingsIgnored(true);
                 durationMillis *= EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER;
             }
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 2aa53cc..a5de90c 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -169,6 +169,8 @@
     }
 
     public void rebindIfDisconnected() {
+        //TODO: When we are connecting to the service, calling this will unbind and bind again.
+        // We'd better not unbind if we are connecting.
         if (mActiveConnection == null && shouldBind()) {
             unbind();
             bind();
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index fe118e5..b688e09 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -83,7 +83,7 @@
 
             // Scan packages.
             // Also has the side-effect of restarting providers if needed.
-            mHandler.post(mScanPackagesRunnable);
+            postScanPackagesIfNeeded();
         }
     }
 
@@ -92,7 +92,7 @@
             mRunning = false;
 
             mContext.unregisterReceiver(mScanPackagesReceiver);
-            mHandler.removeCallbacks(mScanPackagesRunnable);
+            mHandler.removeCallbacks(this::scanPackages);
 
             // Stop all providers.
             for (int i = mProxies.size() - 1; i >= 0; i--) {
@@ -154,20 +154,19 @@
         return -1;
     }
 
+    private void postScanPackagesIfNeeded() {
+        if (!mHandler.hasCallbacks(this::scanPackages)) {
+            mHandler.post(this::scanPackages);
+        }
+    }
+
     private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) {
                 Slog.d(TAG, "Received package manager broadcast: " + intent);
             }
-            scanPackages();
-        }
-    };
-
-    private final Runnable mScanPackagesRunnable = new Runnable() {
-        @Override
-        public void run() {
-            scanPackages();
+            postScanPackagesIfNeeded();
         }
     };
 
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 52e9d7c..c3413e8 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -98,18 +98,26 @@
     public List<MediaRoute2Info> getSystemRoutes() {
         final int uid = Binder.getCallingUid();
         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+        final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+                == PackageManager.PERMISSION_GRANTED;
 
         final long token = Binder.clearCallingIdentity();
         try {
             Collection<MediaRoute2Info> systemRoutes;
             synchronized (mLock) {
                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
-                MediaRoute2ProviderInfo providerInfo =
-                        userRecord.mHandler.mSystemProvider.getProviderInfo();
-                if (providerInfo != null) {
-                    systemRoutes = providerInfo.getRoutes();
+                if (hasModifyAudioRoutingPermission) {
+                    MediaRoute2ProviderInfo providerInfo =
+                            userRecord.mHandler.mSystemProvider.getProviderInfo();
+                    if (providerInfo != null) {
+                        systemRoutes = providerInfo.getRoutes();
+                    } else {
+                        systemRoutes = Collections.emptyList();
+                    }
                 } else {
-                    systemRoutes = Collections.emptyList();
+                    systemRoutes = new ArrayList<>();
+                    systemRoutes.add(userRecord.mHandler.mSystemProvider.getDefaultRoute());
                 }
             }
             return new ArrayList<>(systemRoutes);
@@ -122,18 +130,25 @@
     public RoutingSessionInfo getSystemSessionInfo() {
         final int uid = Binder.getCallingUid();
         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+        final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+                == PackageManager.PERMISSION_GRANTED;
 
         final long token = Binder.clearCallingIdentity();
         try {
             RoutingSessionInfo systemSessionInfo = null;
             synchronized (mLock) {
                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
-                List<RoutingSessionInfo> sessionInfos =
-                        userRecord.mHandler.mSystemProvider.getSessionInfos();
-                if (sessionInfos != null && !sessionInfos.isEmpty()) {
-                    systemSessionInfo = sessionInfos.get(0);
+                List<RoutingSessionInfo> sessionInfos;
+                if (hasModifyAudioRoutingPermission) {
+                    sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
+                    if (sessionInfos != null && !sessionInfos.isEmpty()) {
+                        systemSessionInfo = sessionInfos.get(0);
+                    } else {
+                        Slog.w(TAG, "System provider does not have any session info.");
+                    }
                 } else {
-                    Slog.w(TAG, "System provider does not have any session info.");
+                    systemSessionInfo = userRecord.mHandler.mSystemProvider.getDefaultSessionInfo();
                 }
             }
             return systemSessionInfo;
@@ -654,10 +669,20 @@
             return;
         }
 
-        routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::transferToRouteOnHandler,
-                        routerRecord.mUserRecord.mHandler,
-                        DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
+        String defaultRouteId =
+                routerRecord.mUserRecord.mHandler.mSystemProvider.getDefaultRoute().getId();
+        if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission
+                && !TextUtils.equals(route.getId(), defaultRouteId)) {
+            routerRecord.mUserRecord.mHandler.sendMessage(
+                    obtainMessage(UserHandler::notifySessionCreationFailedToRouter,
+                            routerRecord.mUserRecord.mHandler,
+                            routerRecord, toOriginalRequestId(DUMMY_REQUEST_ID)));
+        } else {
+            routerRecord.mUserRecord.mHandler.sendMessage(
+                    obtainMessage(UserHandler::transferToRouteOnHandler,
+                            routerRecord.mUserRecord.mHandler,
+                            DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
+        }
     }
 
     private void setSessionVolumeWithRouter2Locked(@NonNull IMediaRouter2 router,
@@ -808,7 +833,7 @@
             return;
         }
 
-        // Can be null if the session is system's.
+        // Can be null if the session is system's or RCN.
         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
                 .findRouterforSessionLocked(uniqueSessionId);
 
@@ -829,7 +854,7 @@
             return;
         }
 
-        // Can be null if the session is system's.
+        // Can be null if the session is system's or RCN.
         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
                 .findRouterforSessionLocked(uniqueSessionId);
 
@@ -850,7 +875,7 @@
             return;
         }
 
-        // Can be null if the session is system's.
+        // Can be null if the session is system's or RCN.
         RouterRecord routerRecord = managerRecord.mUserRecord.mHandler
                 .findRouterforSessionLocked(uniqueSessionId);
 
@@ -1185,18 +1210,42 @@
                 }
             }
 
-            List<IMediaRouter2> routers = getRouters();
+            List<IMediaRouter2> routersWithModifyAudioRoutingPermission = getRouters(true);
+            List<IMediaRouter2> routersWithoutModifyAudioRoutingPermission = getRouters(false);
             List<IMediaRouter2Manager> managers = getManagers();
+            List<MediaRoute2Info> defaultRoute = new ArrayList<>();
+            defaultRoute.add(mSystemProvider.getDefaultRoute());
+
             if (addedRoutes.size() > 0) {
-                notifyRoutesAddedToRouters(routers, addedRoutes);
+                notifyRoutesAddedToRouters(routersWithModifyAudioRoutingPermission, addedRoutes);
+                if (!provider.mIsSystemRouteProvider) {
+                    notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
+                            addedRoutes);
+                } else if (prevInfo == null) {
+                    notifyRoutesAddedToRouters(routersWithoutModifyAudioRoutingPermission,
+                            defaultRoute);
+                } // 'else' is handled as changed routes
                 notifyRoutesAddedToManagers(managers, addedRoutes);
             }
             if (removedRoutes.size() > 0) {
-                notifyRoutesRemovedToRouters(routers, removedRoutes);
+                notifyRoutesRemovedToRouters(routersWithModifyAudioRoutingPermission,
+                        removedRoutes);
+                if (!provider.mIsSystemRouteProvider) {
+                    notifyRoutesRemovedToRouters(routersWithoutModifyAudioRoutingPermission,
+                            removedRoutes);
+                }
                 notifyRoutesRemovedToManagers(managers, removedRoutes);
             }
             if (changedRoutes.size() > 0) {
-                notifyRoutesChangedToRouters(routers, changedRoutes);
+                notifyRoutesChangedToRouters(routersWithModifyAudioRoutingPermission,
+                        changedRoutes);
+                if (!provider.mIsSystemRouteProvider) {
+                    notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
+                            changedRoutes);
+                } else if (prevInfo != null) {
+                    notifyRoutesChangedToRouters(routersWithoutModifyAudioRoutingPermission,
+                            defaultRoute);
+                } // 'else' is handled as added routes
                 notifyRoutesChangedToManagers(managers, changedRoutes);
             }
         }
@@ -1223,6 +1272,15 @@
                         toOriginalRequestId(uniqueRequestId));
                 return;
             }
+            if (route.isSystemRoute() && !routerRecord.mHasModifyAudioRoutingPermission
+                    && !TextUtils.equals(route.getId(),
+                            mSystemProvider.getDefaultRoute().getId())) {
+                Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
+                        + route);
+                notifySessionCreationFailedToRouter(routerRecord,
+                        toOriginalRequestId(uniqueRequestId));
+                return;
+            }
 
             SessionCreationRequest request =
                     new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord);
@@ -1232,7 +1290,7 @@
                     route.getOriginalId(), sessionHints);
         }
 
-        // routerRecord can be null if the session is system's.
+        // routerRecord can be null if the session is system's or RCN.
         private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord,
                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
@@ -1250,7 +1308,7 @@
                     route.getOriginalId());
         }
 
-        // routerRecord can be null if the session is system's.
+        // routerRecord can be null if the session is system's or RCN.
         private void deselectRouteOnHandler(long uniqueRequestId,
                 @Nullable RouterRecord routerRecord,
                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
@@ -1270,7 +1328,7 @@
                     route.getOriginalId());
         }
 
-        // routerRecord can be null if the session is system's.
+        // routerRecord can be null if the session is system's or RCN.
         private void transferToRouteOnHandler(long uniqueRequestId,
                 @Nullable RouterRecord routerRecord,
                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
@@ -1289,6 +1347,8 @@
                     route.getOriginalId());
         }
 
+        // routerRecord is null if and only if the session is created without the request, which
+        // includes the system's session and RCN cases.
         private boolean checkArgumentsForSessionControl(@Nullable RouterRecord routerRecord,
                 @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route,
                 @NonNull String description) {
@@ -1305,12 +1365,6 @@
                 return true;
             }
 
-            //TODO(b/152950479): Handle RCN case.
-            if (routerRecord == null) {
-                Slog.w(TAG, "Ignoring " + description + " route from unknown router.");
-                return false;
-            }
-
             RouterRecord matchingRecord = mSessionToRouterMap.get(uniqueSessionId);
             if (matchingRecord != routerRecord) {
                 Slog.w(TAG, "Ignoring " + description + " route from non-matching router. "
@@ -1448,7 +1502,9 @@
                 if (service == null) {
                     return;
                 }
-                notifySessionInfoChangedToRouters(getRouters(), sessionInfo);
+                notifySessionInfoChangedToRouters(getRouters(true), sessionInfo);
+                notifySessionInfoChangedToRouters(getRouters(false),
+                        mSystemProvider.getDefaultSessionInfo());
                 return;
             }
 
@@ -1569,7 +1625,7 @@
             }
         }
 
-        private List<IMediaRouter2> getRouters() {
+        private List<IMediaRouter2> getAllRouters() {
             final List<IMediaRouter2> routers = new ArrayList<>();
             MediaRouter2ServiceImpl service = mServiceRef.get();
             if (service == null) {
@@ -1583,6 +1639,23 @@
             return routers;
         }
 
+        private List<IMediaRouter2> getRouters(boolean hasModifyAudioRoutingPermission) {
+            final List<IMediaRouter2> routers = new ArrayList<>();
+            MediaRouter2ServiceImpl service = mServiceRef.get();
+            if (service == null) {
+                return routers;
+            }
+            synchronized (service.mLock) {
+                for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
+                    if (hasModifyAudioRoutingPermission
+                            == routerRecord.mHasModifyAudioRoutingPermission) {
+                        routers.add(routerRecord.mRouter);
+                    }
+                }
+            }
+            return routers;
+        }
+
         private List<IMediaRouter2Manager> getManagers() {
             final List<IMediaRouter2Manager> managers = new ArrayList<>();
             MediaRouter2ServiceImpl service = mServiceRef.get();
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 5b16d68..6e2feeb 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -72,6 +72,7 @@
     // This should be the currently selected route.
     MediaRoute2Info mDefaultRoute;
     MediaRoute2Info mDeviceRoute;
+    RoutingSessionInfo mDefaultSessionInfo;
     final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
 
     final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
@@ -114,6 +115,7 @@
             }
         });
         updateSessionInfosIfNeeded();
+
         mContext.registerReceiver(new VolumeChangeReceiver(),
                 new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
 
@@ -156,6 +158,10 @@
 
     @Override
     public void transferToRoute(long requestId, String sessionId, String routeId) {
+        if (TextUtils.equals(routeId, DEFAULT_ROUTE_ID)) {
+            // The currently selected route is the default route.
+            return;
+        }
         if (mBtRouteProvider != null) {
             if (TextUtils.equals(routeId, mDeviceRoute.getId())) {
                 mBtRouteProvider.transferTo(null);
@@ -182,6 +188,10 @@
         return mDefaultRoute;
     }
 
+    public RoutingSessionInfo getDefaultSessionInfo() {
+        return mDefaultSessionInfo;
+    }
+
     private void updateDeviceRoute(AudioRoutesInfo newRoutes) {
         int name = R.string.default_audio_route_name;
         if (newRoutes != null) {
@@ -229,8 +239,6 @@
      */
     boolean updateSessionInfosIfNeeded() {
         synchronized (mLock) {
-            // Prevent to execute this method before mBtRouteProvider is created.
-            if (mBtRouteProvider == null) return false;
             RoutingSessionInfo oldSessionInfo = mSessionInfos.isEmpty() ? null : mSessionInfos.get(
                     0);
 
@@ -238,14 +246,19 @@
                     SYSTEM_SESSION_ID, "" /* clientPackageName */)
                     .setSystemSession(true);
 
-            MediaRoute2Info selectedRoute = mBtRouteProvider.getSelectedRoute();
-            if (selectedRoute == null) {
-                selectedRoute = mDeviceRoute;
-            } else {
-                builder.addTransferableRoute(mDeviceRoute.getId());
+            MediaRoute2Info selectedRoute = mDeviceRoute;
+            if (mBtRouteProvider != null) {
+                MediaRoute2Info selectedBtRoute = mBtRouteProvider.getSelectedRoute();
+                if (selectedBtRoute != null) {
+                    selectedRoute = selectedBtRoute;
+                    builder.addTransferableRoute(mDeviceRoute.getId());
+                }
             }
             mSelectedRouteId = selectedRoute.getId();
-            mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute).build();
+            mDefaultRoute = new MediaRoute2Info.Builder(DEFAULT_ROUTE_ID, selectedRoute)
+                    .setSystemRoute(true)
+                    .setProviderId(mUniqueId)
+                    .build();
             builder.addSelectedRoute(mSelectedRouteId);
 
             for (MediaRoute2Info route : mBtRouteProvider.getTransferableRoutes()) {
@@ -258,6 +271,12 @@
             } else {
                 mSessionInfos.clear();
                 mSessionInfos.add(newSessionInfo);
+                mDefaultSessionInfo = new RoutingSessionInfo.Builder(
+                        SYSTEM_SESSION_ID, "" /* clientPackageName */)
+                        .setProviderId(mUniqueId)
+                        .setSystemSession(true)
+                        .addSelectedRoute(DEFAULT_ROUTE_ID)
+                        .build();
                 return true;
             }
         }
@@ -302,6 +321,9 @@
                 } else if (mBtRouteProvider != null) {
                     mBtRouteProvider.setSelectedRouteVolume(newVolume);
                 }
+                mDefaultRoute = new MediaRoute2Info.Builder(mDefaultRoute)
+                        .setVolume(newVolume)
+                        .build();
                 publishProviderState();
             }
         }
diff --git a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
new file mode 100644
index 0000000..0bdf3f2
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java
@@ -0,0 +1,211 @@
+/*
+ * 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.net;
+
+import static android.net.NetworkTemplate.getCollapsedRatType;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.telephony.Annotation;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+/**
+ * Helper class that watches for events that are triggered per subscription.
+ */
+// TODO (b/152176562): Write tests to verify subscription changes generate corresponding
+//  register/unregister calls.
+public class NetworkStatsSubscriptionsMonitor extends
+        SubscriptionManager.OnSubscriptionsChangedListener {
+
+    /**
+     * Interface that this monitor uses to delegate event handling to NetworkStatsService.
+     */
+    public interface Delegate {
+        /**
+         * Notify that the collapsed RAT type has been changed for any subscription. The method
+         * will also be triggered for any existing sub when start and stop monitoring.
+         *
+         * @param subscriberId IMSI of the subscription.
+         * @param collapsedRatType collapsed RAT type.
+         *                         @see android.net.NetworkTemplate#getCollapsedRatType(int).
+         */
+        void onCollapsedRatTypeChanged(@NonNull String subscriberId,
+                @Annotation.NetworkType int collapsedRatType);
+    }
+    private final Delegate mDelegate;
+
+    /**
+     * Receivers that watches for {@link ServiceState} changes for each subscription, to
+     * monitor the transitioning between Radio Access Technology(RAT) types for each sub.
+     */
+    @NonNull
+    private final CopyOnWriteArrayList<RatTypeListener> mRatListeners =
+            new CopyOnWriteArrayList<>();
+
+    @NonNull
+    private final SubscriptionManager mSubscriptionManager;
+    @NonNull
+    private final TelephonyManager mTeleManager;
+
+    @NonNull
+    private final Executor mExecutor;
+
+    NetworkStatsSubscriptionsMonitor(@NonNull Context context, @NonNull Executor executor,
+            @NonNull Delegate delegate) {
+        super();
+        mSubscriptionManager = (SubscriptionManager) context.getSystemService(
+                Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+        mTeleManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        mExecutor = executor;
+        mDelegate = delegate;
+    }
+
+    @Override
+    public void onSubscriptionsChanged() {
+        // Collect active subId list, hidden subId such as opportunistic subscriptions are
+        // also needed to track CBRS.
+        final List<Integer> newSubs = getActiveSubIdList(mSubscriptionManager);
+
+        for (final int subId : newSubs) {
+            final RatTypeListener match = CollectionUtils.find(mRatListeners,
+                    it -> it.mSubId == subId);
+            if (match != null) continue;
+
+            // Create listener for every newly added sub. Also store subscriberId into it to
+            // prevent binder call to telephony when querying RAT.
+            final String subscriberId = mTeleManager.getSubscriberId(subId);
+            if (TextUtils.isEmpty(subscriberId)) {
+                Log.wtf(NetworkStatsService.TAG,
+                        "Empty subscriberId for newly added sub: " + subId);
+            }
+            final RatTypeListener listener =
+                    new RatTypeListener(mExecutor, this, subId, subscriberId);
+            mRatListeners.add(listener);
+
+            // Register listener to the telephony manager that associated with specific sub.
+            mTeleManager.createForSubscriptionId(subId)
+                    .listen(listener, PhoneStateListener.LISTEN_SERVICE_STATE);
+        }
+
+        for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
+            // If the new list contains the subId of the listener, keeps it.
+            final Integer match = CollectionUtils.find(newSubs, it -> it == listener.mSubId);
+            if (match != null) continue;
+
+            handleRemoveRatTypeListener(listener);
+        }
+    }
+
+    @NonNull
+    private List<Integer> getActiveSubIdList(@NonNull SubscriptionManager subscriptionManager) {
+        final ArrayList<Integer> ret = new ArrayList<>();
+        final int[] ids = subscriptionManager.getCompleteActiveSubscriptionIdList();
+        for (int id : ids) ret.add(id);
+        return ret;
+    }
+
+    /**
+     * Get a collapsed RatType for the given subscriberId.
+     *
+     * @param subscriberId the target subscriberId
+     * @return collapsed RatType for the given subscriberId
+     */
+    public int getRatTypeForSubscriberId(@NonNull String subscriberId) {
+        final RatTypeListener match = CollectionUtils.find(mRatListeners,
+                it -> TextUtils.equals(subscriberId, it.mSubscriberId));
+        return match != null ? match.mLastCollapsedRatType : TelephonyManager.NETWORK_TYPE_UNKNOWN;
+    }
+
+    /**
+     * Start monitoring events that triggered per subscription.
+     */
+    public void start() {
+        mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor, this);
+    }
+
+    /**
+     * Unregister subscription changes and all listeners for each subscription.
+     */
+    public void stop() {
+        mSubscriptionManager.removeOnSubscriptionsChangedListener(this);
+
+        for (final RatTypeListener listener : new ArrayList<>(mRatListeners)) {
+            handleRemoveRatTypeListener(listener);
+        }
+    }
+
+    private void handleRemoveRatTypeListener(@NonNull RatTypeListener listener) {
+        mTeleManager.createForSubscriptionId(listener.mSubId)
+                .listen(listener, PhoneStateListener.LISTEN_NONE);
+        mRatListeners.remove(listener);
+
+        // Removal of subscriptions doesn't generate RAT changed event, fire it for every
+        // RatTypeListener.
+        mDelegate.onCollapsedRatTypeChanged(
+                listener.mSubscriberId, TelephonyManager.NETWORK_TYPE_UNKNOWN);
+    }
+
+    static class RatTypeListener extends PhoneStateListener {
+        // Unique id for the subscription. See {@link SubscriptionInfo#getSubscriptionId}.
+        @NonNull
+        private final int mSubId;
+
+        // IMSI to identifying the corresponding network from {@link NetworkState}.
+        // See {@link TelephonyManager#getSubscriberId}.
+        @NonNull
+        private final String mSubscriberId;
+
+        private volatile int mLastCollapsedRatType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+        @NonNull
+        private final NetworkStatsSubscriptionsMonitor mMonitor;
+
+        RatTypeListener(@NonNull Executor executor,
+                @NonNull NetworkStatsSubscriptionsMonitor monitor, int subId,
+                @NonNull String subscriberId) {
+            super(executor);
+            mSubId = subId;
+            mSubscriberId = subscriberId;
+            mMonitor = monitor;
+        }
+
+        @Override
+        public void onServiceStateChanged(@NonNull ServiceState ss) {
+            final int networkType = ss.getDataNetworkType();
+            final int collapsedRatType = getCollapsedRatType(networkType);
+            if (collapsedRatType == mLastCollapsedRatType) return;
+
+            if (NetworkStatsService.LOGD) {
+                Log.d(NetworkStatsService.TAG, "subtype changed for sub(" + mSubId + "): "
+                        + mLastCollapsedRatType + " -> " + collapsedRatType);
+            }
+            mLastCollapsedRatType = collapsedRatType;
+            mMonitor.mDelegate.onCollapsedRatTypeChanged(mSubscriberId, mLastCollapsedRatType);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 43fc7ed..90c85ad 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -63,20 +63,23 @@
         mIdmapDaemon = IdmapDaemon.getInstance();
     }
 
+    /**
+     * Creates the idmap for the target/overlay combination and returns whether the idmap file was
+     * modified.
+     */
     boolean createIdmap(@NonNull final PackageInfo targetPackage,
             @NonNull final PackageInfo overlayPackage, int userId) {
         if (DEBUG) {
             Slog.d(TAG, "create idmap for " + targetPackage.packageName + " and "
                     + overlayPackage.packageName);
         }
-        final int sharedGid = UserHandle.getSharedAppGid(targetPackage.applicationInfo.uid);
         final String targetPath = targetPackage.applicationInfo.getBaseCodePath();
         final String overlayPath = overlayPackage.applicationInfo.getBaseCodePath();
         try {
             int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId);
             boolean enforce = enforceOverlayable(overlayPackage);
             if (mIdmapDaemon.verifyIdmap(targetPath, overlayPath, policies, enforce, userId)) {
-                return true;
+                return false;
             }
             return mIdmapDaemon.createIdmap(targetPath, overlayPath, policies,
                     enforce, userId) != null;
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index d108e76..05a4a38 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -700,14 +700,15 @@
         final PackageInfo overlayPackage = mPackageManager.getPackageInfo(overlayPackageName,
                 userId);
 
-        // Immutable 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.
+        boolean modified = false;
         if (targetPackage != null && overlayPackage != null
                 && !("android".equals(targetPackageName)
                     && !isPackageConfiguredMutable(overlayPackageName))) {
-            mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
+            modified |= mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
         }
 
-        boolean modified = false;
         if (overlayPackage != null) {
             modified |= mSettings.setBaseCodePath(overlayPackageName, userId,
                     overlayPackage.applicationInfo.getBaseCodePath());
diff --git a/services/core/java/com/android/server/om/TEST_MAPPING b/services/core/java/com/android/server/om/TEST_MAPPING
index 75229a1..6edd76f 100644
--- a/services/core/java/com/android/server/om/TEST_MAPPING
+++ b/services/core/java/com/android/server/om/TEST_MAPPING
@@ -15,6 +15,9 @@
       "name": "OverlayHostTests"
     },
     {
+      "name": "OverlayRemountedTest"
+    },
+    {
       "name": "CtsAppSecurityHostTestCases",
       "options": [
         {
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 1e4dc7b..daf4bf2 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -399,7 +399,9 @@
         @VisibleForTesting
         protected IApexService waitForApexService() {
             try {
-                return IApexService.Stub.asInterface(Binder.waitForService("apexservice"));
+                // Since apexd is a trusted platform component, synchronized calls are allowable
+                return IApexService.Stub.asInterface(
+                        Binder.allowBlocking(Binder.waitForService("apexservice")));
             } catch (RemoteException e) {
                 throw new IllegalStateException("Required service apexservice not available");
             }
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index ba7583f..dab4bfd 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -46,6 +46,7 @@
 
 import java.io.File;
 import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
@@ -106,6 +107,8 @@
     private static final long mDowngradeUnusedAppsThresholdInMillis =
             getDowngradeUnusedAppsThresholdInMillis();
 
+    private static List<PackagesUpdatedListener> sPackagesUpdatedListeners = new ArrayList<>();
+
     public static void schedule(Context context) {
         if (isBackgroundDexoptDisabled()) {
             return;
@@ -244,6 +247,7 @@
             }
         }
         notifyPinService(updatedPackages);
+        notifyPackagesUpdated(updatedPackages);
         // Ran to completion, so we abandon our timeslice and do not reschedule.
         jobFinished(jobParams, /* reschedule */ false);
     }
@@ -391,6 +395,7 @@
         } finally {
             // Always let the pinner service know about changes.
             notifyPinService(updatedPackages);
+            notifyPackagesUpdated(updatedPackages);
         }
     }
 
@@ -642,6 +647,32 @@
         }
     }
 
+    public static interface PackagesUpdatedListener {
+        /** Callback when packages have been updated by the bg-dexopt service. */
+        public void onPackagesUpdated(ArraySet<String> updatedPackages);
+    }
+
+    public static void addPackagesUpdatedListener(PackagesUpdatedListener listener) {
+        synchronized (sPackagesUpdatedListeners) {
+            sPackagesUpdatedListeners.add(listener);
+        }
+    }
+
+    public static void removePackagesUpdatedListener(PackagesUpdatedListener listener) {
+        synchronized (sPackagesUpdatedListeners) {
+            sPackagesUpdatedListeners.remove(listener);
+        }
+    }
+
+    /** Notify all listeners (#addPackagesUpdatedListener) that packages have been updated. */
+    private void notifyPackagesUpdated(ArraySet<String> updatedPackages) {
+        synchronized (sPackagesUpdatedListeners) {
+            for (PackagesUpdatedListener listener : sPackagesUpdatedListeners) {
+                listener.onPackagesUpdated(updatedPackages);
+            }
+        }
+    }
+
     private static long getDowngradeUnusedAppsThresholdInMillis() {
         final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
         String sysPropValue = SystemProperties.get(sysPropKey);
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index f37af3a..9fb468e 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -724,6 +724,30 @@
     }
 
     /**
+     * Deletes all snapshots of credential encrypted user data, where the snapshot id is not
+     * included in {@code retainSnapshotIds}.
+     *
+     * @param userId id of the user whose user data snapshots to delete.
+     * @param retainSnapshotIds ids of the snapshots that should not be deleted.
+     *
+     * @return {@code true} if the operation was successful, or {@code false} if a remote call
+     * shouldn't be continued. See {@link #checkBeforeRemote}.
+     *
+     * @throws InstallerException if failed to delete user data snapshot.
+     */
+    public boolean destroyCeSnapshotsNotSpecified(@UserIdInt int userId,
+            int[] retainSnapshotIds) throws InstallerException {
+        if (!checkBeforeRemote()) return false;
+
+        try {
+            mInstalld.destroyCeSnapshotsNotSpecified(null, userId, retainSnapshotIds);
+            return true;
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
+    /**
      * Migrates obb data from its legacy location {@code /data/media/obb} to
      * {@code /data/media/0/Android/obb}. This call is idempotent and a fast no-op if data has
      * already been migrated.
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index e7d0c41..65b7cf3 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -51,6 +51,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.os.storage.StorageManager;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -79,7 +80,7 @@
  * Helper class for running dexopt command on packages.
  */
 public class PackageDexOptimizer {
-    private static final String TAG = "PackageManager.DexOptimizer";
+    private static final String TAG = "PackageDexOptimizer";
     static final String OAT_DIR_NAME = "oat";
     // TODO b/19550105 Remove error codes and use exceptions
     public static final int DEX_OPT_SKIPPED = 0;
@@ -307,6 +308,55 @@
         }
     }
 
+    /**
+     * Perform dexopt (if needed) on a system server code path).
+     */
+    public int dexoptSystemServerPath(
+            String dexPath, PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
+        int dexoptFlags = DEXOPT_PUBLIC
+                | (options.isBootComplete() ? DEXOPT_BOOTCOMPLETE : 0)
+                | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0);
+
+        int result = DEX_OPT_SKIPPED;
+        for (String isa : dexUseInfo.getLoaderIsas()) {
+            int dexoptNeeded = getDexoptNeeded(
+                    dexPath,
+                    isa,
+                    options.getCompilerFilter(),
+                    dexUseInfo.getClassLoaderContext(),
+                    /* newProfile= */false,
+                    /* downgrade= */ false);
+
+            if (dexoptNeeded == DexFile.NO_DEXOPT_NEEDED) {
+                continue;
+            }
+            try {
+                mInstaller.dexopt(
+                        dexPath,
+                        android.os.Process.SYSTEM_UID,
+                        /* packageName= */ "android",
+                        isa,
+                        dexoptNeeded,
+                        /* oatDir= */ null,
+                        dexoptFlags,
+                        options.getCompilerFilter(),
+                        StorageManager.UUID_PRIVATE_INTERNAL,
+                        dexUseInfo.getClassLoaderContext(),
+                        /* seInfo= */ null,
+                        /* downgrade= */ false ,
+                        /* targetSdk= */ 0,
+                        /* profileName */ null,
+                        /* dexMetadataPath */ null,
+                        getReasonName(options.getCompilationReason()));
+            } catch (InstallerException e) {
+                Slog.w(TAG, "Failed to dexopt", e);
+                return DEX_OPT_FAILED;
+            }
+            result = DEX_OPT_PERFORMED;
+        }
+        return result;
+    }
+
     private String getAugmentedReasonName(int compilationReason, boolean useDexMetadata) {
         String annotation = useDexMetadata
                 ? ArtManagerService.DEXOPT_REASON_WITH_DEX_METADATA_ANNOTATION : "";
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index f07fa50..2221644 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -128,6 +128,7 @@
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.pm.Installer.InstallerException;
@@ -1801,6 +1802,15 @@
         }
     }
 
+    private void logDataLoaderInstallationSession(int returnCode, String extraMessage) {
+        final long currentTimestamp = System.currentTimeMillis();
+        FrameworkStatsLog.write(FrameworkStatsLog.PACKAGE_INSTALLER_V2_REPORTED,
+                isIncrementalInstallation(),
+                mPackageName,
+                currentTimestamp - createdMillis,
+                returnCode);
+    }
+
     /**
      * Returns true if the session should attempt to inherit any existing native libraries already
      * extracted at the current install location. This is necessary to prevent double loading of
@@ -2789,6 +2799,9 @@
         }
 
         mCallback.onSessionFinished(this, success);
+        if (isDataLoaderInstallation()) {
+            logDataLoaderInstallationSession(returnCode, msg);
+        }
     }
 
     /** {@hide} */
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f693555..9c41e6d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -9855,6 +9855,11 @@
 
     private int performDexOptInternalWithDependenciesLI(AndroidPackage p,
             @NonNull PackageSetting pkgSetting, DexoptOptions options) {
+        // System server gets a special path.
+        if (PLATFORM_PACKAGE_NAME.equals(p.getPackageName())) {
+            return mDexManager.dexoptSystemServer(options);
+        }
+
         // Select the dex optimizer based on the force parameter.
         // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
         //       allocate an object here.
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index ebdf856..eb7057d 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -49,6 +49,8 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -437,15 +439,7 @@
      *         because they don't need to be compiled)..
      */
     public boolean dexoptSecondaryDex(DexoptOptions options) {
-        // Select the dex optimizer based on the force parameter.
-        // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
-        // the necessary dexopt flags to make sure that compilation is not skipped. This avoid
-        // passing the force flag through the multitude of layers.
-        // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
-        //       allocate an object here.
-        PackageDexOptimizer pdo = options.isForce()
-                ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
-                : mPackageDexOptimizer;
+        PackageDexOptimizer pdo = getPackageDexOptimizer(options);
         String packageName = options.getPackageName();
         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
         if (useInfo.getDexUseInfoMap().isEmpty()) {
@@ -486,6 +480,83 @@
     }
 
     /**
+     * Performs dexopt on system server dex files.
+     *
+     * <p>Verfifies that the package name is {@link PackageManagerService#PLATFORM_PACKAGE_NAME}.
+     *
+     * @return
+     * <p>PackageDexOptimizer.DEX_OPT_SKIPPED if dexopt was skipped because no system server
+     * files were recorded or if no dexopt was needed.
+     * <p>PackageDexOptimizer.DEX_OPT_FAILED if any dexopt operation failed.
+     * <p>PackageDexOptimizer.DEX_OPT_PERFORMED if all dexopt operations succeeded.
+     */
+    public int dexoptSystemServer(DexoptOptions options) {
+        if (!PLATFORM_PACKAGE_NAME.equals(options.getPackageName())) {
+            Slog.wtf(TAG, "Non system server package used when trying to dexopt system server:"
+                    + options.getPackageName());
+            return PackageDexOptimizer.DEX_OPT_FAILED;
+        }
+
+        PackageDexOptimizer pdo = getPackageDexOptimizer(options);
+        String packageName = options.getPackageName();
+        PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
+        if (useInfo.getDexUseInfoMap().isEmpty()) {
+            if (DEBUG) {
+                Slog.d(TAG, "No dex files recorded for system server");
+            }
+            // Nothing to compile, return true.
+            return PackageDexOptimizer.DEX_OPT_SKIPPED;
+        }
+
+        boolean usageUpdated = false;
+        int result = PackageDexOptimizer.DEX_OPT_SKIPPED;
+        for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
+            String dexPath = entry.getKey();
+            DexUseInfo dexUseInfo = entry.getValue();
+            if (!Files.exists(Paths.get(dexPath))) {
+                if (DEBUG) {
+                    Slog.w(TAG, "A dex file previously loaded by System Server does not exist "
+                            + " anymore: " + dexPath);
+                }
+                usageUpdated = mPackageDexUsage.removeDexFile(
+                            packageName, dexPath, dexUseInfo.getOwnerUserId()) || usageUpdated;
+                continue;
+            }
+
+            int newResult = pdo.dexoptSystemServerPath(dexPath, dexUseInfo, options);
+
+            // The end result is:
+            //  - FAILED if any path failed,
+            //  - PERFORMED if at least one path needed compilation,
+            //  - SKIPPED when all paths are up to date
+            if ((result != PackageDexOptimizer.DEX_OPT_FAILED)
+                    && (newResult != PackageDexOptimizer.DEX_OPT_SKIPPED)) {
+                result = newResult;
+            }
+        }
+
+        if (usageUpdated) {
+            mPackageDexUsage.maybeWriteAsync();
+        }
+
+        return result;
+    }
+
+    /**
+     * Select the dex optimizer based on the force parameter.
+     * Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
+     * the necessary dexopt flags to make sure that compilation is not skipped. This avoid
+     * passing the force flag through the multitude of layers.
+     * Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
+     *       allocate an object here.
+     */
+    private PackageDexOptimizer getPackageDexOptimizer(DexoptOptions options) {
+        return options.isForce()
+                ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
+                : mPackageDexOptimizer;
+    }
+
+    /**
      * Reconcile the information we have about the secondary dex files belonging to
      * {@code packagName} and the actual dex files. For all dex files that were
      * deleted, update the internal records and delete any generated oat files.
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index a726d39..e3faffa 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -882,7 +882,7 @@
 
     public void grantDefaultPermissionsToDefaultBrowser(String packageName, int userId) {
         Log.i(TAG, "Granting permissions to default browser for user:" + userId);
-        grantPermissionsToSystemPackage(packageName, userId, ALWAYS_LOCATION_PERMISSIONS);
+        grantPermissionsToSystemPackage(packageName, userId, FOREGROUND_LOCATION_PERMISSIONS);
     }
 
     private String getDefaultSystemHandlerActivityPackage(String intentAction, int userId) {
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 161f304..27288d8 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -30,6 +30,7 @@
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.AppOpsManagerInternal;
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -47,6 +48,7 @@
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
 import android.permission.PermissionControllerManager;
+import android.provider.Settings;
 import android.provider.Telephony;
 import android.telecom.TelecomManager;
 import android.util.ArrayMap;
@@ -70,7 +72,9 @@
 import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
 
 /**
@@ -180,8 +184,6 @@
         intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         intentFilter.addDataScheme("package");
 
-
-        /* TODO ntmyren: enable receiver when test flakes are fixed
         getContext().registerReceiverAsUser(new BroadcastReceiver() {
             final List<Integer> mUserSetupUids = new ArrayList<>(200);
             final Map<UserHandle, PermissionControllerManager> mPermControllerManagers =
@@ -232,7 +234,6 @@
                 manager.updateUserSensitiveForApp(uid);
             }
         }, UserHandle.ALL, intentFilter, null, null);
-         */
     }
 
     /**
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index cd6c5bf..5a3464d 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -3219,6 +3219,10 @@
     private void shutdownOrRebootInternal(final @HaltMode int haltMode, final boolean confirm,
             @Nullable final String reason, boolean wait) {
         if (PowerManager.REBOOT_USERSPACE.equals(reason)) {
+            if (!PowerManager.isRebootingUserspaceSupportedImpl()) {
+                throw new UnsupportedOperationException(
+                        "Attempted userspace reboot on a device that doesn't support it");
+            }
             UserspaceRebootLogger.noteUserspaceRebootWasRequested();
         }
         if (mHandler == null || !mSystemReady) {
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index 5f871ad4..83e99b0 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -505,6 +505,11 @@
             rollbackIds[i] = mRollbacks.get(i).info.getRollbackId();
         }
         ApexManager.getInstance().destroyCeSnapshotsNotSpecified(userId, rollbackIds);
+        try {
+            mInstaller.destroyCeSnapshotsNotSpecified(userId, rollbackIds);
+        } catch (Installer.InstallerException ie) {
+            Slog.e(TAG, "Failed to delete snapshots for user: " + userId, ie);
+        }
     }
 
     @WorkerThread
diff --git a/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java b/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java
new file mode 100644
index 0000000..7cdb84b
--- /dev/null
+++ b/services/core/java/com/android/server/stats/pull/SettingsStatsUtil.java
@@ -0,0 +1,220 @@
+/*
+ * 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.stats.pull;
+
+import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE;
+import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE;
+import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE;
+import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Slog;
+import android.util.StatsEvent;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.service.nano.StringListParamProto;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility methods for creating {@link StatsEvent} data.
+ */
+final class SettingsStatsUtil {
+    private static final String TAG = "SettingsStatsUtil";
+    private static final FlagsData[] GLOBAL_SETTINGS = new FlagsData[]{
+            new FlagsData("GlobalFeature__boolean_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE),
+            new FlagsData("GlobalFeature__integer_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE),
+            new FlagsData("GlobalFeature__float_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE),
+            new FlagsData("GlobalFeature__string_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE)
+    };
+    private static final FlagsData[] SECURE_SETTINGS = new FlagsData[]{
+            new FlagsData("SecureFeature__boolean_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE),
+            new FlagsData("SecureFeature__integer_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE),
+            new FlagsData("SecureFeature__float_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE),
+            new FlagsData("SecureFeature__string_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE)
+    };
+    private static final FlagsData[] SYSTEM_SETTINGS = new FlagsData[]{
+            new FlagsData("SystemFeature__boolean_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE),
+            new FlagsData("SystemFeature__integer_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE),
+            new FlagsData("SystemFeature__float_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE),
+            new FlagsData("SystemFeature__string_whitelist",
+                    SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE)
+    };
+
+    @VisibleForTesting
+    @NonNull
+    static List<StatsEvent> logGlobalSettings(Context context, int atomTag, int userId) {
+        final List<StatsEvent> output = new ArrayList<>();
+        final ContentResolver resolver = context.getContentResolver();
+
+        for (FlagsData flagsData : GLOBAL_SETTINGS) {
+            StringListParamProto proto = getList(flagsData.mFlagName);
+            if (proto == null) {
+                continue;
+            }
+            for (String key : proto.element) {
+                final String value = Settings.Global.getStringForUser(resolver, key, userId);
+                output.add(createStatsEvent(atomTag, key, value, userId,
+                        flagsData.mDataType));
+            }
+        }
+        return output;
+    }
+
+    @NonNull
+    static List<StatsEvent> logSystemSettings(Context context, int atomTag, int userId) {
+        final List<StatsEvent> output = new ArrayList<>();
+        final ContentResolver resolver = context.getContentResolver();
+
+        for (FlagsData flagsData : SYSTEM_SETTINGS) {
+            StringListParamProto proto = getList(flagsData.mFlagName);
+            if (proto == null) {
+                continue;
+            }
+            for (String key : proto.element) {
+                final String value = Settings.System.getStringForUser(resolver, key, userId);
+                output.add(createStatsEvent(atomTag, key, value, userId,
+                        flagsData.mDataType));
+            }
+        }
+        return output;
+    }
+
+    @NonNull
+    static List<StatsEvent> logSecureSettings(Context context, int atomTag, int userId) {
+        final List<StatsEvent> output = new ArrayList<>();
+        final ContentResolver resolver = context.getContentResolver();
+
+        for (FlagsData flagsData : SECURE_SETTINGS) {
+            StringListParamProto proto = getList(flagsData.mFlagName);
+            if (proto == null) {
+                continue;
+            }
+            for (String key : proto.element) {
+                final String value = Settings.Secure.getStringForUser(resolver, key, userId);
+                output.add(createStatsEvent(atomTag, key, value, userId,
+                        flagsData.mDataType));
+            }
+        }
+        return output;
+    }
+
+    @VisibleForTesting
+    @Nullable
+    static StringListParamProto getList(String flag) {
+        final String base64 = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, flag);
+        if (TextUtils.isEmpty(base64)) {
+            return null;
+        }
+        final byte[] decode = Base64.decode(base64, Base64.NO_PADDING | Base64.NO_WRAP);
+        StringListParamProto list = null;
+        try {
+            list = StringListParamProto.parseFrom(decode);
+        } catch (Exception e) {
+            Slog.e(TAG, "Error parsing string list proto", e);
+        }
+        return list;
+    }
+
+    /**
+     * Create {@link StatsEvent} for SETTING_SNAPSHOT atom
+     */
+    @NonNull
+    private static StatsEvent createStatsEvent(int atomTag, String key, String value, int userId,
+            int type) {
+        final StatsEvent.Builder builder = StatsEvent.newBuilder()
+                .setAtomId(atomTag)
+                .writeString(key);
+        boolean booleanValue = false;
+        int intValue = 0;
+        float floatValue = 0;
+        String stringValue = "";
+        if (TextUtils.isEmpty(value)) {
+            builder.writeInt(FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__NOTASSIGNED)
+                    .writeBoolean(booleanValue)
+                    .writeInt(intValue)
+                    .writeFloat(floatValue)
+                    .writeString(stringValue)
+                    .writeInt(userId);
+        } else {
+            switch (type) {
+                case SETTING_SNAPSHOT__TYPE__ASSIGNED_BOOL_TYPE:
+                    booleanValue = "1".equals(value);
+                    break;
+                case FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_INT_TYPE:
+                    try {
+                        intValue = Integer.parseInt(value);
+                    } catch (NumberFormatException e) {
+                        Slog.w(TAG, "Can not parse value to float: " + value);
+                    }
+                    break;
+                case SETTING_SNAPSHOT__TYPE__ASSIGNED_FLOAT_TYPE:
+                    try {
+                        floatValue = Float.parseFloat(value);
+                    } catch (NumberFormatException e) {
+                        Slog.w(TAG, "Can not parse value to float: " + value);
+                    }
+                    break;
+                case FrameworkStatsLog.SETTING_SNAPSHOT__TYPE__ASSIGNED_STRING_TYPE:
+                    stringValue = value;
+                    break;
+                default:
+                    Slog.w(TAG, "Unexpected value type " + type);
+            }
+            builder.writeInt(type)
+                    .writeBoolean(booleanValue)
+                    .writeInt(intValue)
+                    .writeFloat(floatValue)
+                    .writeString(stringValue)
+                    .writeInt(userId);
+        }
+        return builder.build();
+    }
+
+    /** Class for defining flag name and its data type. */
+    static final class FlagsData {
+        /** {@link DeviceConfig} flag name, value of the flag is {@link StringListParamProto} */
+        String mFlagName;
+        /** Data type of the value getting from {@link Settings} keys. */
+        int mDataType;
+
+        FlagsData(String flagName, int dataType) {
+            mFlagName = flagName;
+            mDataType = dataType;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index e9da2c4..288c22a 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -416,6 +416,8 @@
                         return pullHealthHal(atomTag, data);
                     case FrameworkStatsLog.ATTRIBUTED_APP_OPS:
                         return pullAttributedAppOps(atomTag, data);
+                    case FrameworkStatsLog.SETTING_SNAPSHOT:
+                        return pullSettingsStats(atomTag, data);
                     default:
                         throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
                 }
@@ -580,6 +582,7 @@
         registerFullBatteryCapacity();
         registerBatteryVoltage();
         registerBatteryCycleCount();
+        registerSettingsStats();
     }
 
     /**
@@ -3244,6 +3247,43 @@
         return StatsManager.PULL_SUCCESS;
     }
 
+    private void registerSettingsStats() {
+        int tagId = FrameworkStatsLog.SETTING_SNAPSHOT;
+        mStatsManager.setPullAtomCallback(
+                tagId,
+                null, // use default PullAtomMetadata values
+                BackgroundThread.getExecutor(),
+                mStatsCallbackImpl
+        );
+    }
+
+    int pullSettingsStats(int atomTag, List<StatsEvent> pulledData) {
+        UserManager userManager = mContext.getSystemService(UserManager.class);
+        if (userManager == null) {
+            return StatsManager.PULL_SKIP;
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            for (UserInfo user : userManager.getUsers()) {
+                final int userId = user.getUserHandle().getIdentifier();
+
+                if (userId == UserHandle.USER_SYSTEM) {
+                    pulledData.addAll(SettingsStatsUtil.logGlobalSettings(mContext, atomTag,
+                            UserHandle.USER_SYSTEM));
+                }
+                pulledData.addAll(SettingsStatsUtil.logSystemSettings(mContext, atomTag, userId));
+                pulledData.addAll(SettingsStatsUtil.logSecureSettings(mContext, atomTag, userId));
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "failed to pullSettingsStats", e);
+            return StatsManager.PULL_SKIP;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
     // Thermal event received from vendor thermal management subsystem
     private static final class ThermalEventListener extends IThermalEventListener.Stub {
         @Override
diff --git a/services/core/java/com/android/server/textclassifier/IconsContentProvider.java b/services/core/java/com/android/server/textclassifier/IconsContentProvider.java
new file mode 100644
index 0000000..d19a707
--- /dev/null
+++ b/services/core/java/com/android/server/textclassifier/IconsContentProvider.java
@@ -0,0 +1,124 @@
+/*
+ * 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.textclassifier;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.textclassifier.IconsUriHelper.ResourceInfo;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * A content provider that is used to access icons returned from the TextClassifier service.
+ *
+ * <p>Use {@link IconsUriHelper#getContentUri(String, int)} to access a uri for a specific resource.
+ * The uri may be passed to other processes to access the specified resource.
+ *
+ * <p>NOTE: Care must be taken to avoid leaking resources to non-permitted apps via this provider.
+ */
+public final class IconsContentProvider extends ContentProvider {
+
+    private static final String TAG = "IconsContentProvider";
+
+    @Override
+    public ParcelFileDescriptor openFile(Uri uri, String mode) {
+        try {
+            final ResourceInfo res = IconsUriHelper.getInstance().getResourceInfo(uri);
+            final Drawable drawable = Icon.createWithResource(res.packageName, res.id)
+                    .loadDrawable(getContext());
+            final byte[] data = getBitmapData(drawable);
+            final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+            final ParcelFileDescriptor readSide = pipe[0];
+            final ParcelFileDescriptor writeSide = pipe[1];
+            try (OutputStream out = new AutoCloseOutputStream(writeSide)) {
+                out.write(data);
+                return readSide;
+            }
+        } catch (IOException | RuntimeException e) {
+            Log.e(TAG, "Error retrieving icon for uri: " + uri, e);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the bitmap data for the specified drawable.
+     */
+    @VisibleForTesting
+    public static byte[] getBitmapData(Drawable drawable) {
+        if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+            throw new IllegalStateException("The icon is zero-sized");
+        }
+
+        final Bitmap bitmap = Bitmap.createBitmap(
+                drawable.getIntrinsicWidth(),
+                drawable.getIntrinsicHeight(),
+                Bitmap.Config.ARGB_8888);
+
+        final Canvas canvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        drawable.draw(canvas);
+
+        final ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
+        final byte[] byteArray = stream.toByteArray();
+        bitmap.recycle();
+        return byteArray;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return "image/png";
+    }
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/services/core/java/com/android/server/textclassifier/IconsUriHelper.java b/services/core/java/com/android/server/textclassifier/IconsUriHelper.java
new file mode 100644
index 0000000..f17b0f1
--- /dev/null
+++ b/services/core/java/com/android/server/textclassifier/IconsUriHelper.java
@@ -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.
+ */
+package com.android.server.textclassifier;
+
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.annotations.VisibleForTesting.Visibility;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.function.Supplier;
+
+/**
+ * A helper for mapping an icon resource to a content uri.
+ *
+ * <p>NOTE: Care must be taken to avoid passing resource uris to non-permitted apps via this helper.
+ */
+@VisibleForTesting(visibility = Visibility.PACKAGE)
+public final class IconsUriHelper {
+
+    public static final String AUTHORITY = "com.android.textclassifier.icons";
+
+    private static final String TAG = "IconsUriHelper";
+    private static final Supplier<String> DEFAULT_ID_SUPPLIER = () -> UUID.randomUUID().toString();
+
+    // TODO: Consider using an LRU cache to limit resource usage.
+    // This may depend on the expected number of packages that a device typically has.
+    @GuardedBy("mPackageIds")
+    private final Map<String, String> mPackageIds = new ArrayMap<>();
+
+    private final Supplier<String> mIdSupplier;
+
+    private static final IconsUriHelper sSingleton = new IconsUriHelper(null);
+
+    private IconsUriHelper(@Nullable Supplier<String> idSupplier) {
+        mIdSupplier = (idSupplier != null) ? idSupplier : DEFAULT_ID_SUPPLIER;
+
+        // Useful for testing:
+        // Magic id for the android package so it is the same across classloaders.
+        // This is okay as this package does not have access restrictions, and
+        // the TextClassifierService hardly returns icons from this package.
+        mPackageIds.put("android", "android");
+    }
+
+    /**
+     * Returns a new instance of this object for testing purposes.
+     */
+    public static IconsUriHelper newInstanceForTesting(@Nullable Supplier<String> idSupplier) {
+        return new IconsUriHelper(idSupplier);
+    }
+
+    static IconsUriHelper getInstance() {
+        return sSingleton;
+    }
+
+    /**
+     * Returns a Uri for the specified icon resource.
+     *
+     * @param packageName the resource's package name
+     * @param resId       the resource id
+     * @see #getResourceInfo(Uri)
+     */
+    public Uri getContentUri(String packageName, int resId) {
+        Objects.requireNonNull(packageName);
+        synchronized (mPackageIds) {
+            if (!mPackageIds.containsKey(packageName)) {
+                // TODO: Ignore packages that don't actually exist on the device.
+                mPackageIds.put(packageName, mIdSupplier.get());
+            }
+            return new Uri.Builder()
+                    .scheme("content")
+                    .authority(AUTHORITY)
+                    .path(mPackageIds.get(packageName))
+                    .appendPath(Integer.toString(resId))
+                    .build();
+        }
+    }
+
+    /**
+     * Returns a valid {@link ResourceInfo} for the specified uri. Returns {@code null} if a valid
+     * {@link ResourceInfo} cannot be returned for the specified uri.
+     *
+     * @see #getContentUri(String, int);
+     */
+    @Nullable
+    public ResourceInfo getResourceInfo(Uri uri) {
+        if (!"content".equals(uri.getScheme())) {
+            return null;
+        }
+        if (!AUTHORITY.equals(uri.getAuthority())) {
+            return null;
+        }
+
+        final List<String> pathItems = uri.getPathSegments();
+        try {
+            synchronized (mPackageIds) {
+                final String packageId = pathItems.get(0);
+                final int resId = Integer.parseInt(pathItems.get(1));
+                for (String packageName : mPackageIds.keySet()) {
+                    if (packageId.equals(mPackageIds.get(packageName))) {
+                        return new ResourceInfo(packageName, resId);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            Log.v(TAG, "Could not get resource info. Reason: " + e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * A holder for a resource's package name and id.
+     */
+    public static final class ResourceInfo {
+
+        public final String packageName;
+        public final int id;
+
+        private ResourceInfo(String packageName, int id) {
+            this.packageName = Objects.requireNonNull(packageName);
+            this.id = id;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c47d215..ed9b019 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5212,6 +5212,12 @@
         updateReportedVisibilityLocked();
     }
 
+    void onStartingWindowDrawn() {
+        if (task != null) {
+            task.setHasBeenVisible(true);
+        }
+    }
+
     /** Called when the windows associated app window container are drawn. */
     void onWindowsDrawn(boolean drawn, long timestampNs) {
         mDrawn = drawn;
@@ -6484,11 +6490,18 @@
         final Rect containingBounds = mTmpBounds;
         mCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
                 orientation, orientationRequested, canChangeOrientation);
-        resolvedBounds.set(containingAppBounds);
+        resolvedBounds.set(containingBounds);
         // The size of floating task is fixed (only swap), so the aspect ratio is already correct.
         if (!mCompatDisplayInsets.mIsFloating) {
             applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds);
         }
+        // If the bounds are restricted by fixed aspect ratio, the resolved bounds should be put in
+        // the container app bounds. Otherwise the entire container bounds are available.
+        final boolean fillContainer = resolvedBounds.equals(containingBounds);
+        if (!fillContainer) {
+            // The horizontal position should not cover insets.
+            resolvedBounds.left = containingAppBounds.left;
+        }
 
         // Use resolvedBounds to compute other override configurations such as appBounds. The bounds
         // are calculated in compat container space. The actual position on screen will be applied
@@ -6557,7 +6570,7 @@
         final int offsetX = getHorizontalCenterOffset(
                 (int) viewportW, (int) (contentW * mSizeCompatScale));
         // Above coordinates are in "@" space, now place "*" and "#" to screen space.
-        final int screenPosX = parentAppBounds.left + offsetX;
+        final int screenPosX = (fillContainer ? parentBounds.left : parentAppBounds.left) + offsetX;
         final int screenPosY = parentBounds.top;
         if (screenPosX != 0 || screenPosY != 0) {
             if (mSizeCompatBounds != null) {
@@ -7355,7 +7368,8 @@
         }
         final ActivityStack stack = getRootTask();
         return stack != null &&
-                stack.checkKeyguardVisibility(this, true /* shouldBeVisible */, true /* isTop */);
+                stack.checkKeyguardVisibility(this, true /* shouldBeVisible */,
+                        stack.topRunningActivity() == this /* isTop */);
     }
 
     void setTurnScreenOn(boolean turnScreenOn) {
@@ -7654,8 +7668,6 @@
                 // Ensure the app bounds won't overlap with insets.
                 Task.intersectWithInsetsIfFits(outAppBounds, outBounds, mNonDecorInsets[rotation]);
             }
-            // The horizontal position is centered and it should not cover insets.
-            outBounds.left = outAppBounds.left;
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index e5ff865..0f52248 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -715,9 +715,13 @@
 
     private void setWindowingModeInSurfaceTransaction(int preferredWindowingMode,
             boolean creating) {
+        final TaskDisplayArea taskDisplayArea = getDisplayArea();
+        if (taskDisplayArea == null) {
+            Slog.d(TAG, "taskDisplayArea is null, bail early");
+            return;
+        }
         final int currentMode = getWindowingMode();
         final int currentOverrideMode = getRequestedOverrideWindowingMode();
-        final TaskDisplayArea taskDisplayArea = getDisplayArea();
         final Task topTask = getTopMostTask();
         int windowingMode = preferredWindowingMode;
         if (preferredWindowingMode == WINDOWING_MODE_UNDEFINED
@@ -753,7 +757,7 @@
                 // warning toast about it.
                 mAtmService.getTaskChangeNotificationController()
                         .notifyActivityDismissingDockedStack();
-                taskDisplayArea.onSplitScreenModeDismissed();
+                taskDisplayArea.onSplitScreenModeDismissed(this);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 1e5a924..9de8ef7 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -1469,7 +1469,7 @@
             if (toDisplay.getDisplayId() != stack.getDisplayId()) {
                 stack.reparent(toDisplay.getDefaultTaskDisplayArea(), false /* onTop */);
             } else {
-                toDisplay.mTaskContainers.positionStackAtBottom(stack);
+                toDisplay.getDefaultTaskDisplayArea().positionStackAtBottom(stack);
             }
 
             mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
@@ -2198,7 +2198,7 @@
                 // split-screen in split-screen.
                 mService.getTaskChangeNotificationController()
                         .notifyActivityDismissingDockedStack();
-                taskDisplayArea.onSplitScreenModeDismissed();
+                taskDisplayArea.onSplitScreenModeDismissed(task.getStack());
                 taskDisplayArea.mDisplayContent.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
                         true /* notifyClients */);
             }
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index d777f3f..4e85837 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -19,6 +19,7 @@
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL;
 
@@ -191,8 +192,9 @@
         try {
             // TODO(multi-display-area): Support starting home in a task display area
             // Make sure home stack exist on display.
+            // TODO(b/153624902): Replace with TaskDisplayArea#getOrCreateRootHomeTask()
             homeStack = display.getDefaultTaskDisplayArea().getOrCreateStack(
-                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
+                    WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, ON_TOP);
         } finally {
             mSupervisor.endDeferResume();
         }
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index f86aeb2..6f1ddcd 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -76,7 +76,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
-import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
 
 import android.annotation.DrawableRes;
@@ -404,11 +403,18 @@
                 mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN;
     }
 
-
     boolean isNextAppTransitionOpenCrossProfileApps() {
         return mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS;
     }
 
+    boolean isNextAppTransitionCustomFromRecents() {
+        final RecentTasks recentTasks = mService.mAtmService.getRecentTasks();
+        final String recentsPackageName =
+                (recentTasks != null) ? recentTasks.getRecentsComponent().getPackageName() : null;
+        return mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM
+                && mNextAppTransitionPackage.equals(recentsPackageName);
+    }
+
     /**
      * @return true if and only if we are currently fetching app transition specs from the future
      *         passed into {@link #overridePendingAppTransitionMultiThumbFuture}
@@ -1807,15 +1813,11 @@
     }
 
     int getAppStackClipMode() {
-        // When dismiss keyguard animation occurs, clip before the animation to prevent docked
-        // app from showing beyond the divider
-        if (mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY
-                || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER) {
-            return STACK_CLIP_BEFORE_ANIM;
-        }
         return mNextAppTransition == TRANSIT_ACTIVITY_RELAUNCH
                 || mNextAppTransition == TRANSIT_DOCK_TASK_FROM_RECENTS
                 || mNextAppTransitionType == NEXT_TRANSIT_TYPE_CLIP_REVEAL
+                || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY
+                || mNextAppTransition == TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER
                 ? STACK_CLIP_NONE
                 : STACK_CLIP_AFTER_ANIM;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index edd14b7..a512337 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -54,7 +54,6 @@
  * - BELOW_TASKS: Can only contain BELOW_TASK DisplayAreas and WindowTokens that go below tasks.
  * - ABOVE_TASKS: Can only contain ABOVE_TASK DisplayAreas and WindowTokens that go above tasks.
  * - ANY: Can contain any kind of DisplayArea, and any kind of WindowToken or the Task container.
- *        Cannot have a sibling that is of type ANY.
  *
  * @param <T> type of the children of the DisplayArea.
  */
@@ -274,7 +273,6 @@
         ANY;
 
         static void checkSiblings(Type bottom, Type top) {
-            checkState(!(bottom == ANY && top == ANY), "ANY cannot be a sibling of ANY");
             checkState(!(bottom != BELOW_TASKS && top == BELOW_TASKS),
                     bottom + " must be above BELOW_TASKS");
             checkState(!(bottom == ABOVE_TASKS && top != ABOVE_TASKS),
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
index 9821573..15b6f973 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -16,9 +16,14 @@
 
 package com.android.server.wm;
 
+import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
+
 import android.content.res.Resources;
 import android.text.TextUtils;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Policy that manages DisplayAreas.
  */
@@ -37,9 +42,9 @@
     protected final DisplayArea<? extends WindowContainer> mImeContainer;
 
     /**
-     * The Tasks container. Tasks etc. are automatically added to this container.
+     * The task display areas. Tasks etc. are automatically added to these containers.
      */
-    protected final DisplayArea<? extends ActivityStack> mTaskContainers;
+    protected final List<TaskDisplayArea> mTaskDisplayAreas;
 
     /**
      * Construct a new {@link DisplayAreaPolicy}
@@ -48,19 +53,19 @@
      * @param content the display content for which the policy applies
      * @param root the root display area under which the policy operates
      * @param imeContainer the ime container that the policy must attach
-     * @param taskDisplayArea the task container that the policy must attach
+     * @param taskDisplayAreas the task display areas that the policy must attach
      *
      * @see #attachDisplayAreas()
      */
     protected DisplayAreaPolicy(WindowManagerService wmService,
             DisplayContent content, DisplayArea.Root root,
             DisplayArea<? extends WindowContainer> imeContainer,
-            DisplayArea<? extends ActivityStack> taskDisplayArea) {
+            List<TaskDisplayArea> taskDisplayAreas) {
         mWmService = wmService;
         mContent = content;
         mRoot = root;
         mImeContainer = imeContainer;
-        mTaskContainers = taskDisplayArea;
+        mTaskDisplayAreas = taskDisplayAreas;
     }
 
     /**
@@ -80,15 +85,32 @@
      */
     public abstract void addWindow(WindowToken token);
 
+    /**
+     * @return the number of task display areas on the display.
+     */
+    public int getTaskDisplayAreaCount() {
+        return mTaskDisplayAreas.size();
+    }
+
+    /**
+     * @return the task display area at index.
+     */
+    public TaskDisplayArea getTaskDisplayAreaAt(int index) {
+        return mTaskDisplayAreas.get(index);
+    }
+
     /** Provider for platform-default display area policy. */
     static final class DefaultProvider implements DisplayAreaPolicy.Provider {
         @Override
         public DisplayAreaPolicy instantiate(WindowManagerService wmService,
                 DisplayContent content, DisplayArea.Root root,
-                DisplayArea<? extends WindowContainer> imeContainer,
-                TaskDisplayArea taskDisplayArea) {
+                DisplayArea<? extends WindowContainer> imeContainer) {
+            final TaskDisplayArea defaultTaskDisplayArea = new TaskDisplayArea(content, wmService,
+                    "DefaultTaskDisplayArea", FEATURE_DEFAULT_TASK_CONTAINER);
+            final List<TaskDisplayArea> tdaList = new ArrayList<>();
+            tdaList.add(defaultTaskDisplayArea);
             return new DisplayAreaPolicyBuilder()
-                    .build(wmService, content, root, imeContainer, taskDisplayArea);
+                    .build(wmService, content, root, imeContainer, tdaList);
         }
     }
 
@@ -107,8 +129,7 @@
          */
         DisplayAreaPolicy instantiate(WindowManagerService wmService,
                 DisplayContent content, DisplayArea.Root root,
-                DisplayArea<? extends WindowContainer> imeContainer,
-                TaskDisplayArea taskDisplayArea);
+                DisplayArea<? extends WindowContainer> imeContainer);
 
         /**
          * Instantiate the device-specific {@link Provider}.
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
index e8becfa..2c2f433 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -201,8 +201,8 @@
 
         Result(WindowManagerService wmService, DisplayContent content, DisplayArea.Root root,
                 DisplayArea<? extends WindowContainer> imeContainer,
-                DisplayArea<? extends ActivityStack> taskDisplayArea, ArrayList<Feature> features) {
-            super(wmService, content, root, imeContainer, taskDisplayArea);
+                List<TaskDisplayArea> taskDisplayAreas, ArrayList<Feature> features) {
+            super(wmService, content, root, imeContainer, taskDisplayAreas);
             mFeatures = features;
             mAreas = new HashMap<>(features.size());
             for (int i = 0; i < mFeatures.size(); i++) {
@@ -267,7 +267,7 @@
                     areaForLayer[layer].mChildren.add(leafArea);
                     leafType = type;
                     if (leafType == LEAF_TYPE_TASK_CONTAINERS) {
-                        leafArea.mExisting = mTaskContainers;
+                        addTaskDisplayAreasToLayer(areaForLayer[layer], layer);
                     } else if (leafType == LEAF_TYPE_IME_CONTAINERS) {
                         leafArea.mExisting = mImeContainer;
                     }
@@ -278,6 +278,17 @@
             root.instantiateChildren(mRoot, mAreaForLayer, 0, mAreas);
         }
 
+        /** Adds all task display areas to the specified layer */
+        private void addTaskDisplayAreasToLayer(PendingArea parentPendingArea, int layer) {
+            final int count = mTaskDisplayAreas.size();
+            for (int i = 0; i < count; i++) {
+                PendingArea leafArea = new PendingArea(null, layer, parentPendingArea);
+                leafArea.mExisting = mTaskDisplayAreas.get(i);
+                leafArea.mMaxLayer = layer;
+                parentPendingArea.mChildren.add(leafArea);
+            }
+        }
+
         @Override
         public void addWindow(WindowToken token) {
             DisplayArea.Tokens area = findAreaForToken(token);
@@ -317,12 +328,16 @@
         return this;
     }
 
+    protected List<Feature> getFeatures() {
+        return mFeatures;
+    }
+
     Result build(WindowManagerService wmService,
             DisplayContent content, DisplayArea.Root root,
             DisplayArea<? extends WindowContainer> imeContainer,
-            DisplayArea<? extends ActivityStack> taskDisplayArea) {
+            List<TaskDisplayArea> taskDisplayAreas) {
 
-        return new Result(wmService, content, root, imeContainer, taskDisplayArea, new ArrayList<>(
+        return new Result(wmService, content, root, imeContainer, taskDisplayAreas, new ArrayList<>(
                 mFeatures));
     }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a93b962..80a1a45 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -72,6 +72,7 @@
 import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
+import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
@@ -269,8 +270,6 @@
             new NonAppWindowContainers("mOverlayContainers", mWmService);
 
     /** The containers below are the only child containers {@link #mWindowContainers} can have. */
-    // Contains all window containers that are related to apps (Activities)
-    final TaskDisplayArea mTaskContainers = new TaskDisplayArea(this, mWmService);
 
     // Contains all IME window containers. Note that the z-ordering of the IME windows will depend
     // on the IME target. We mainly have this container grouping so we can keep track of all the IME
@@ -972,7 +971,7 @@
         super.addChild(mOverlayContainers, null);
 
         mDisplayAreaPolicy = mWmService.mDisplayAreaPolicyProvider.instantiate(
-                mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers);
+                mWmService, this, mRootDisplayArea, mImeWindowsContainers);
         mWindowContainers.addChildren();
 
         // Sets the display content for the children.
@@ -1082,8 +1081,7 @@
             return null;
         }
         mShellRoots.put(windowType, root);
-        SurfaceControl out = new SurfaceControl();
-        out.copyFrom(rootLeash);
+        SurfaceControl out = new SurfaceControl(rootLeash);
         return out;
     }
 
@@ -2071,13 +2069,11 @@
     }
 
     protected int getTaskDisplayAreaCount() {
-        // TODO(multi-display-area): Report actual display area count
-        return 1;
+        return mDisplayAreaPolicy.getTaskDisplayAreaCount();
     }
 
     protected TaskDisplayArea getTaskDisplayAreaAt(int index) {
-        // TODO(multi-display-area): Report actual display area values
-        return mTaskContainers;
+        return mDisplayAreaPolicy.getTaskDisplayAreaAt(index);
     }
 
     ActivityStack getStack(int rootTaskId) {
@@ -2403,7 +2399,7 @@
      * or for cases when multi-instance is not supported yet (like Split-screen, PiP or Recents).
      */
     TaskDisplayArea getDefaultTaskDisplayArea() {
-        return mTaskContainers;
+        return mDisplayAreaPolicy.getTaskDisplayAreaAt(0);
     }
 
     @Override
@@ -3554,6 +3550,10 @@
                         onWallpaper, goingToShade, subtle));
             }
         }, true /* traverseTopToBottom */);
+        for (int i = mShellRoots.size() - 1; i >= 0; --i) {
+            mShellRoots.valueAt(i).startAnimation(policy.createHiddenByKeyguardExit(
+                    onWallpaper, goingToShade, subtle));
+        }
     }
 
     /** @return {@code true} if there is window to wait before enabling the screen. */
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index a031fe8..0a9878d 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -442,10 +442,6 @@
             // Always prepare an app transition since we rely on the transition callbacks to cleanup
             mWindowManager.prepareAppTransition(TRANSIT_NONE, false);
             controller.setCancelOnNextTransitionStart();
-        } else {
-            // Just cancel directly to unleash from launcher when the next launching task is the
-            // current top task.
-            mWindowManager.cancelRecentsAnimation(REORDER_KEEP_IN_PLACE, "stackOrderChanged");
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 6fda117..54210ae 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -43,6 +43,7 @@
 import android.os.SystemClock;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.IntArray;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
 import android.util.proto.ProtoOutputStream;
@@ -99,6 +100,8 @@
     private IRecentsAnimationRunner mRunner;
     private final RecentsAnimationCallbacks mCallbacks;
     private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
+    private final IntArray mPendingNewTaskTargets = new IntArray(0);
+
     private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
             new ArrayList<>();
     private final int mDisplayId;
@@ -220,6 +223,10 @@
                     if (mCanceled) {
                         return;
                     }
+                    // Remove all new task targets.
+                    for (int i = mPendingNewTaskTargets.size() - 1; i >= 0; i--) {
+                        removeTaskInternal(mPendingNewTaskTargets.get(i));
+                    }
                 }
 
                 // Note, the callback will handle its own synchronization, do not lock on WM lock
@@ -310,6 +317,18 @@
                 mWillFinishToHome = willFinishToHome;
             }
         }
+
+        @Override
+        public boolean removeTask(int taskId) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mService.getWindowManagerLock()) {
+                    return removeTaskInternal(taskId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
     };
 
     /**
@@ -405,11 +424,17 @@
 
     @VisibleForTesting
     AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) {
+        return addAnimation(task, isRecentTaskInvisible, null /* finishedCallback */);
+    }
+
+    @VisibleForTesting
+    AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible,
+            OnAnimationFinishedCallback finishedCallback) {
         ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addAnimation(%s)", task.getName());
         final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task,
                 isRecentTaskInvisible);
         task.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */,
-                ANIMATION_TYPE_RECENTS);
+                ANIMATION_TYPE_RECENTS, finishedCallback);
         task.commitPendingTransaction();
         mPendingAnimations.add(taskAdapter);
         return taskAdapter;
@@ -489,6 +514,49 @@
         }
     }
 
+    void addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback) {
+        if (mRunner != null) {
+            final RemoteAnimationTarget target = createTaskRemoteAnimation(task, finishedCallback);
+            if (target == null) return;
+
+            ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addTaskToTargets, target: %s", target);
+            try {
+                mRunner.onTaskAppeared(target);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to report task appeared", e);
+            }
+        }
+    }
+
+    private RemoteAnimationTarget createTaskRemoteAnimation(Task task,
+            OnAnimationFinishedCallback finishedCallback) {
+        final SparseBooleanArray recentTaskIds =
+                mService.mAtmService.getRecentTasks().getRecentTaskIds();
+        TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task,
+                !recentTaskIds.get(task.mTaskId), finishedCallback);
+        mPendingNewTaskTargets.add(task.mTaskId);
+        return adapter.createRemoteAnimationTarget();
+    }
+
+    private boolean removeTaskInternal(int taskId) {
+        boolean result = false;
+        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
+            // Only allows when task target has became visible to user, to prevent
+            // the flickering during remove animation and task visible.
+            final TaskAnimationAdapter target = mPendingAnimations.get(i);
+            if (target.mTask.mTaskId == taskId && target.mTask.isOnTop()) {
+                removeAnimation(target);
+                final int taskIndex = mPendingNewTaskTargets.indexOf(taskId);
+                if (taskIndex != -1) {
+                    mPendingNewTaskTargets.remove(taskIndex);
+                }
+                result = true;
+                break;
+            }
+        }
+        return result;
+    }
+
     private RemoteAnimationTarget[] createAppAnimations() {
         final ArrayList<RemoteAnimationTarget> targets = new ArrayList<>();
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 80b8b58..26d6c78 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -23,7 +23,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -38,6 +37,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
 import static android.view.WindowManager.TRANSIT_CRASHING_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
 
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
@@ -66,6 +66,7 @@
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC;
 import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
+import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE;
 import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT;
 import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER;
 import static com.android.server.wm.RootWindowContainerProto.PENDING_ACTIVITIES;
@@ -664,7 +665,7 @@
 
     void setSecureSurfaceState(int userId, boolean disabled) {
         forAllWindows((w) -> {
-            if (w.mHasSurface && userId == UserHandle.getUserId(w.mOwnerUid)) {
+            if (w.mHasSurface && userId == w.mShowUserId) {
                 w.mWinAnimator.setSecureLocked(disabled);
             }
         }, true /* traverseTopToBottom */);
@@ -1369,8 +1370,7 @@
         calculateDefaultMinimalSizeOfResizeableTasks();
 
         final TaskDisplayArea defaultTaskDisplayArea = getDefaultTaskDisplayArea();
-        defaultTaskDisplayArea.getOrCreateStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME,
-                ON_TOP);
+        defaultTaskDisplayArea.getOrCreateRootHomeTask();
         positionChildAt(POSITION_TOP, defaultTaskDisplayArea.mDisplayContent,
                 false /* includingParents */);
     }
@@ -1515,6 +1515,7 @@
         // Updates the extra information of the intent.
         if (fromHomeKey) {
             homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true);
+            mWindowManager.cancelRecentsAnimation(REORDER_KEEP_IN_PLACE, "startHomeActivity");
         }
         // Update the reason for ANR debugging to verify if the user activity is the one that
         // actually launched.
@@ -2118,16 +2119,19 @@
 
         try {
             final Task task = r.getTask();
-
             final ActivityStack pinnedStack = taskDisplayArea.getRootPinnedTask();
+
             // This will change the pinned stack's windowing mode to its original mode, ensuring
             // we only have one stack that is in pinned mode.
             if (pinnedStack != null) {
                 pinnedStack.dismissPip();
             }
 
-            final boolean singleActivity = task.getChildCount() == 1;
+            // Set a transition to ensure that we don't immediately try and update the visibility
+            // of the activity entering PIP
+            r.getDisplayContent().prepareAppTransition(TRANSIT_NONE, false);
 
+            final boolean singleActivity = task.getChildCount() == 1;
             final ActivityStack stack;
             if (singleActivity) {
                 stack = r.getRootTask();
@@ -2150,11 +2154,6 @@
             mService.continueWindowLayout();
         }
 
-        // TODO: revisit the following statement after the animation is moved from WM to SysUI.
-        // Update the visibility of all activities after the they have been reparented to the new
-        // stack.  This MUST run after the animation above is scheduled to ensure that the windows
-        // drawn signal is scheduled after the bounds animation start call on the bounds animator
-        // thread.
         ensureActivitiesVisible(null, 0, false /* preserveWindows */);
         resumeFocusedStacksTopActivities();
 
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 7d8a56e..56147f2 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -162,7 +162,19 @@
             InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
         return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                 outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
-                outInsetsState, outActiveControls);
+                outInsetsState, outActiveControls, UserHandle.getUserId(mUid));
+    }
+
+
+    @Override
+    public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
+            int viewVisibility, int displayId, int userId, Rect outFrame,
+            Rect outContentInsets, Rect outStableInsets,
+            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
+        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
+                outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
+                outInsetsState, outActiveControls, userId);
     }
 
     @Override
@@ -172,7 +184,7 @@
         return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                 new Rect() /* outFrame */, outContentInsets, outStableInsets,
                 new DisplayCutout.ParcelableWrapper() /* cutout */, null /* outInputChannel */,
-                outInsetsState, null);
+                outInsetsState, null, UserHandle.getUserId(mUid));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
index 9732637..701feff 100644
--- a/services/core/java/com/android/server/wm/ShellRoot.java
+++ b/services/core/java/com/android/server/wm/ShellRoot.java
@@ -16,12 +16,20 @@
 
 package com.android.server.wm;
 
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
+import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
+
 import android.annotation.NonNull;
+import android.graphics.Point;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
+import android.view.DisplayInfo;
 import android.view.IWindow;
 import android.view.SurfaceControl;
+import android.view.animation.Animation;
 
 /**
  * Represents a piece of the hierarchy under which a client Shell can manage sub-windows.
@@ -70,5 +78,29 @@
     IWindow getClient() {
         return mClient;
     }
+
+    void startAnimation(Animation anim) {
+        // Only do this for the divider
+        if (mToken.windowType != TYPE_DOCK_DIVIDER) {
+            return;
+        }
+
+        DisplayInfo displayInfo = mToken.getFixedRotationTransformDisplayInfo();
+        if (displayInfo == null) {
+            displayInfo = mDisplayContent.getDisplayInfo();
+        }
+
+        // Mostly copied from WindowState to enable keyguard transition animation
+        anim.initialize(displayInfo.logicalWidth, displayInfo.logicalHeight,
+                displayInfo.appWidth, displayInfo.appHeight);
+        anim.restrictDuration(MAX_ANIMATION_DURATION);
+        anim.scaleCurrentDuration(mDisplayContent.mWmService.getWindowAnimationScaleLocked());
+        final AnimationAdapter adapter = new LocalAnimationAdapter(
+                new WindowAnimationSpec(anim, new Point(0, 0), false /* canSkipFirstFrame */,
+                        0 /* windowCornerRadius */),
+                mDisplayContent.mWmService.mSurfaceAnimationRunner);
+        mToken.startAnimation(mToken.getPendingTransaction(), adapter, false /* hidden */,
+                ANIMATION_TYPE_WINDOW_ANIMATION, null /* animationFinishedCallback */);
+    }
 }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9adacb8..fd32724 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -86,6 +86,8 @@
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
 import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
+import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
+import static com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback;
 import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
 import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
 import static com.android.server.wm.WindowContainerChildProto.TASK;
@@ -135,6 +137,7 @@
 import android.view.RemoteAnimationTarget;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.view.WindowManager;
 import android.window.ITaskOrganizer;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -2334,32 +2337,30 @@
         return Configuration.reduceScreenLayout(sourceScreenLayout, longSize, shortSize);
     }
 
-    private void resolveOrganizedOverrideConfiguration(Configuration newParentConfig) {
-        super.resolveOverrideConfiguration(newParentConfig);
-        if (!isOrganized()) {
-            return;
-        }
-
-        final Task root = getRootTask();
-        if (root == this) {
-            return;
-        }
-
-        // Ensure to have the same windowing mode for the child tasks that controlled by task org.
-        getResolvedOverrideConfiguration().windowConfiguration
-                .setWindowingMode(root.getWindowingMode());
-    }
-
     @Override
     void resolveOverrideConfiguration(Configuration newParentConfig) {
-        if (!isLeafTask() || mCreatedByOrganizer) {
-            resolveOrganizedOverrideConfiguration(newParentConfig);
-            return;
-        }
         mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
-        resolveOrganizedOverrideConfiguration(newParentConfig);
+        super.resolveOverrideConfiguration(newParentConfig);
+
+        // Resolve override windowing mode to fullscreen for home task (even on freeform
+        // display), or split-screen-secondary if in split-screen mode.
         int windowingMode =
                 getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
+        if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
+            windowingMode = inSplitScreenWindowingMode() ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                    : WINDOWING_MODE_FULLSCREEN;
+            getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
+        }
+
+        if (!isLeafTask()) {
+            // Compute configuration overrides for tasks that created by organizer, so that
+            // organizer can get the correct configuration from those tasks.
+            if (mCreatedByOrganizer) {
+                computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
+            }
+            return;
+        }
+
         if (windowingMode == WINDOWING_MODE_UNDEFINED) {
             windowingMode = newParentConfig.windowConfiguration.getWindowingMode();
         }
@@ -3404,6 +3405,24 @@
     }
 
     @Override
+    protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
+            int transit, boolean isVoiceInteraction,
+            @Nullable OnAnimationFinishedCallback finishedCallback) {
+        final RecentsAnimationController control = mWmService.getRecentsAnimationController();
+        if (control != null && enter
+                && getDisplayContent().mAppTransition.isNextAppTransitionCustomFromRecents()) {
+            ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS,
+                    "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s",
+                    control, asTask(), AppTransition.appTransitionToString(transit));
+            // We let the transition to be controlled by RecentsAnimation, and callback task's
+            // RemoteAnimationTarget for remote runner to animate.
+            control.addTaskToTargets(getRootTask(), finishedCallback);
+        } else {
+            super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, finishedCallback);
+        }
+    }
+
+    @Override
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         super.dump(pw, prefix, dumpAll);
         final String doublePrefix = prefix + "  ";
@@ -4100,8 +4119,18 @@
     }
 
     void setHasBeenVisible(boolean hasBeenVisible) {
+        final boolean prevHasBeenVisible = mHasBeenVisible;
         mHasBeenVisible = hasBeenVisible;
         if (hasBeenVisible) {
+            // If the task is not yet visible when it is added to the task organizer, then we should
+            // hide it to allow the task organizer to show it when it is properly reparented. We
+            // skip this for tasks created by the organizer because they can synchronously update
+            // the leash before new children are added to the task.
+            if (!mCreatedByOrganizer && mTaskOrganizer != null && !prevHasBeenVisible) {
+                getPendingTransaction().hide(getSurfaceControl());
+                commitPendingTransaction();
+            }
+
             sendTaskAppeared();
             if (!isRootTask()) {
                 getRootTask().setHasBeenVisible(true);
@@ -4121,20 +4150,18 @@
      * Any time any of these conditions are updated, the updating code should call
      * sendTaskAppeared.
      */
-    private boolean taskAppearedReady() {
+    boolean taskAppearedReady() {
         return mSurfaceControl != null && mTaskOrganizer != null && getHasBeenVisible();
     }
 
     private void sendTaskAppeared() {
-        if (taskAppearedReady() && !mTaskAppearedSent) {
-            mTaskAppearedSent = true;
+        if (mTaskOrganizer != null) {
             mAtmService.mTaskOrganizerController.onTaskAppeared(mTaskOrganizer, this);
         }
     }
 
     private void sendTaskVanished() {
-        if (mTaskOrganizer != null && mTaskAppearedSent) {
-            mTaskAppearedSent = false;
+        if (mTaskOrganizer != null) {
             mAtmService.mTaskOrganizerController.onTaskVanished(mTaskOrganizer, this);
         }
    }
@@ -4147,6 +4174,8 @@
         // Let the old organizer know it has lost control.
         sendTaskVanished();
         mTaskOrganizer = organizer;
+
+
         sendTaskAppeared();
         onTaskOrganizerChanged();
         return true;
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 7fb6f6f..735bef8 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -31,7 +31,6 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.window.DisplayAreaOrganizer.FEATURE_TASK_CONTAINER;
 
 import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
 import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
@@ -141,8 +140,9 @@
      */
     private boolean mRemoved;
 
-    TaskDisplayArea(DisplayContent displayContent, WindowManagerService service) {
-        super(service, Type.ANY, "TaskContainers", FEATURE_TASK_CONTAINER);
+    TaskDisplayArea(DisplayContent displayContent, WindowManagerService service, String name,
+            int displayAreaFeature) {
+        super(service, Type.ANY, name, displayAreaFeature);
         mDisplayContent = displayContent;
         mRootWindowContainer = service.mRoot;
         mAtmService = service.mAtmService;
@@ -1165,17 +1165,23 @@
     }
 
     void onSplitScreenModeDismissed() {
+        onSplitScreenModeDismissed(null /* toTop */);
+    }
+
+    void onSplitScreenModeDismissed(ActivityStack toTop) {
         mAtmService.deferWindowLayout();
         try {
             mLaunchRootTask = null;
             moveSplitScreenTasksToFullScreen();
         } finally {
-            final ActivityStack topFullscreenStack =
-                    getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
+            final ActivityStack topFullscreenStack = toTop != null
+                    ? toTop : getTopStackInWindowingMode(WINDOWING_MODE_FULLSCREEN);
             final ActivityStack homeStack = getOrCreateRootHomeTask();
-            if (topFullscreenStack != null && homeStack != null && !isTopStack(homeStack)) {
+            if (homeStack != null && ((topFullscreenStack != null && !isTopStack(homeStack))
+                    || toTop != null)) {
                 // Whenever split-screen is dismissed we want the home stack directly behind the
                 // current top fullscreen stack so it shows up when the top stack is finished.
+                // Or, if the caller specified a stack to be on top after split-screen is dismissed.
                 // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
                 // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
                 // once we have that.
@@ -1415,8 +1421,7 @@
         ActivityStack homeTask = getRootHomeTask();
         if (homeTask == null && mDisplayContent.supportsSystemDecorations()
                 && !mDisplayContent.isUntrustedVirtualDisplay()) {
-            homeTask = createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME,
-                    false /* onTop */);
+            homeTask = createStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, false /* onTop */);
         }
         return homeTask;
     }
@@ -1612,6 +1617,11 @@
         }
     }
 
+    @Override
+    boolean canCreateRemoteAnimationTarget() {
+        return true;
+    }
+
     /**
      * Callback for when the order of the stacks in the display changes.
      */
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 2bbf8db..9873031 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -25,6 +25,7 @@
 import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_WINDOW_CONFIGS;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
 import android.content.Intent;
@@ -38,6 +39,7 @@
 import android.window.ITaskOrganizerController;
 import android.window.WindowContainerToken;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 
 import java.io.PrintWriter;
@@ -46,6 +48,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.WeakHashMap;
+import java.util.function.Consumer;
 
 /**
  * Stores the TaskOrganizers associated with a given windowing mode and
@@ -81,17 +84,105 @@
                 }
             }
         }
-    };
+    }
+
+    /**
+     * A wrapper class around ITaskOrganizer to ensure that the calls are made in the right
+     * lifecycle order since we may be updating the visibility of task surface controls in a pending
+     * transaction before they are presented to the task org.
+     */
+    private class TaskOrganizerCallbacks {
+        final WindowManagerService mService;
+        final ITaskOrganizer mTaskOrganizer;
+        final Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
+
+        TaskOrganizerCallbacks(WindowManagerService wm, ITaskOrganizer taskOrg,
+                Consumer<Runnable> deferTaskOrgCallbacksConsumer) {
+            mService = wm;
+            mDeferTaskOrgCallbacksConsumer = deferTaskOrgCallbacksConsumer;
+            mTaskOrganizer = taskOrg;
+        }
+
+        IBinder getBinder() {
+            return mTaskOrganizer.asBinder();
+        }
+
+        void onTaskAppeared(Task task) {
+            final RunningTaskInfo taskInfo = task.getTaskInfo();
+            mDeferTaskOrgCallbacksConsumer.accept(() -> {
+                try {
+                    mTaskOrganizer.onTaskAppeared(taskInfo);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception sending onTaskAppeared callback", e);
+                }
+            });
+        }
+
+
+        void onTaskVanished(Task task) {
+            final RunningTaskInfo taskInfo = task.getTaskInfo();
+            mDeferTaskOrgCallbacksConsumer.accept(() -> {
+                try {
+                    mTaskOrganizer.onTaskVanished(taskInfo);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception sending onTaskVanished callback", e);
+                }
+            });
+        }
+
+        void onTaskInfoChanged(Task task, ActivityManager.RunningTaskInfo taskInfo) {
+            if (!task.mCreatedByOrganizer && !task.mTaskAppearedSent) {
+                // Skip if the task has not yet received taskAppeared(), except for tasks created
+                // by the organizer that don't receive that signal
+                return;
+            }
+            mDeferTaskOrgCallbacksConsumer.accept(() -> {
+                if (!task.isOrganized()) {
+                    // This is safe to ignore if the task is no longer organized
+                    return;
+                }
+                try {
+                    mTaskOrganizer.onTaskInfoChanged(taskInfo);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception sending onTaskInfoChanged callback", e);
+                }
+            });
+        }
+
+        void onBackPressedOnTaskRoot(Task task) {
+            if (!task.mCreatedByOrganizer && !task.mTaskAppearedSent) {
+                // Skip if the task has not yet received taskAppeared(), except for tasks created
+                // by the organizer that don't receive that signal
+                return;
+            }
+            mDeferTaskOrgCallbacksConsumer.accept(() -> {
+                if (!task.isOrganized()) {
+                    // This is safe to ignore if the task is no longer organized
+                    return;
+                }
+                try {
+                   mTaskOrganizer.onBackPressedOnTaskRoot(task.getTaskInfo());
+                } catch (Exception e) {
+                    Slog.e(TAG, "Exception sending onBackPressedOnTaskRoot callback", e);
+                }
+            });
+        }
+    }
 
     private class TaskOrganizerState {
-        private final ITaskOrganizer mOrganizer;
+        private final TaskOrganizerCallbacks mOrganizer;
         private final DeathRecipient mDeathRecipient;
         private final ArrayList<Task> mOrganizedTasks = new ArrayList<>();
         private final int mUid;
         private boolean mInterceptBackPressedOnTaskRoot;
 
         TaskOrganizerState(ITaskOrganizer organizer, int uid) {
-            mOrganizer = organizer;
+            final Consumer<Runnable> deferTaskOrgCallbacksConsumer =
+                    mDeferTaskOrgCallbacksConsumer != null
+                            ? mDeferTaskOrgCallbacksConsumer
+                            : mService.mWindowManager.mAnimator::addAfterPrepareSurfacesRunnable;
+            mOrganizer = new TaskOrganizerCallbacks(mService.mWindowManager, organizer,
+                    deferTaskOrgCallbacksConsumer);
             mDeathRecipient = new DeathRecipient(organizer);
             try {
                 organizer.asBinder().linkToDeath(mDeathRecipient, 0);
@@ -106,19 +197,21 @@
         }
 
         void addTask(Task t) {
-            mOrganizedTasks.add(t);
-            try {
-                mOrganizer.onTaskAppeared(t.getTaskInfo());
-            } catch (Exception e) {
-                Slog.e(TAG, "Exception sending taskAppeared callback" + e);
+            if (t.mTaskAppearedSent) return;
+
+            if (!mOrganizedTasks.contains(t)) {
+                mOrganizedTasks.add(t);
+            }
+            if (t.taskAppearedReady()) {
+                t.mTaskAppearedSent = true;
+                mOrganizer.onTaskAppeared(t);
             }
         }
 
         void removeTask(Task t) {
-            try {
-                mOrganizer.onTaskVanished(t.getTaskInfo());
-            } catch (Exception e) {
-                Slog.e(TAG, "Exception sending taskVanished callback" + e);
+            if (t.mTaskAppearedSent) {
+                t.mTaskAppearedSent = false;
+                mOrganizer.onTaskVanished(t);
             }
             mOrganizedTasks.remove(t);
         }
@@ -126,7 +219,7 @@
         void dispose() {
             releaseTasks();
             for (int i = mTaskOrganizersForWindowingMode.size() - 1; i >= 0; --i) {
-                mTaskOrganizersForWindowingMode.valueAt(i).remove(mOrganizer.asBinder());
+                mTaskOrganizersForWindowingMode.valueAt(i).remove(mOrganizer.getBinder());
             }
         }
 
@@ -139,7 +232,7 @@
         }
 
         void unlinkDeath() {
-            mOrganizer.asBinder().unlinkToDeath(mDeathRecipient, 0);
+            mOrganizer.getBinder().unlinkToDeath(mDeathRecipient, 0);
         }
     }
 
@@ -149,9 +242,10 @@
     private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
     private final ArrayList<Task> mPendingTaskInfoChanges = new ArrayList<>();
 
-    final ActivityTaskManagerService mService;
+    private final ActivityTaskManagerService mService;
 
-    RunningTaskInfo mTmpTaskInfo;
+    private RunningTaskInfo mTmpTaskInfo;
+    private Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
 
     TaskOrganizerController(ActivityTaskManagerService atm) {
         mService = atm;
@@ -163,6 +257,15 @@
     }
 
     /**
+     * Specifies the consumer to run to defer the task org callbacks. Can be overridden while
+     * testing to allow the callbacks to be sent synchronously.
+     */
+    @VisibleForTesting
+    public void setDeferTaskOrgCallbacksConsumer(Consumer<Runnable> consumer) {
+        mDeferTaskOrgCallbacksConsumer = consumer;
+    }
+
+    /**
      * Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
      * If there was already a TaskOrganizer for this windowing mode it will be evicted
      * but will continue to organize it's existing tasks.
@@ -253,7 +356,7 @@
         if (state == null) {
             return null;
         }
-        return state.mOrganizer;
+        return state.mOrganizer.mTaskOrganizer;
     }
 
     void onTaskAppeared(ITaskOrganizer organizer, Task task) {
@@ -358,10 +461,15 @@
         // change.
         mTmpTaskInfo = null;
 
-        if (task.mTaskOrganizer != null) {
-            try {
-                task.mTaskOrganizer.onTaskInfoChanged(newInfo);
-            } catch (RemoteException e) {
+        if (task.isOrganized()) {
+            // Because we defer sending taskAppeared() until the app has drawn, we may receive a
+            // configuration change before the state actually has the task registered. As such we
+            // should ignore these change events to the organizer until taskAppeared(). If the task
+            // was created by the organizer, then we always send the info change.
+            final TaskOrganizerState state = mTaskOrganizerStates.get(
+                    task.mTaskOrganizer.asBinder());
+            if (state != null) {
+                state.mOrganizer.onTaskInfoChanged(task, newInfo);
             }
         }
     }
@@ -521,11 +629,7 @@
             return false;
         }
 
-        try {
-            state.mOrganizer.onBackPressedOnTaskRoot(task.getTaskInfo());
-        } catch (Exception e) {
-            Slog.e(TAG, "Exception sending interceptBackPressedOnTaskRoot callback" + e);
-        }
+        state.mOrganizer.onBackPressedOnTaskRoot(task);
         return true;
     }
 
@@ -542,7 +646,7 @@
                 final TaskOrganizerState state =  mTaskOrganizerStates.get(taskOrgs.get(j));
                 final ArrayList<Task> tasks = state.mOrganizedTasks;
                 pw.print(innerPrefix + "    ");
-                pw.println(state.mOrganizer + " uid=" + state.mUid + ":");
+                pw.println(state.mOrganizer.mTaskOrganizer + " uid=" + state.mUid + ":");
                 for (int k = 0; k < tasks.size(); k++) {
                     pw.println(innerPrefix + "      " + tasks.get(k));
                 }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index f6ed2a9..14e5c6c 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -379,6 +379,7 @@
             frame = null;
             mTmpDstFrame.set(mFrame);
         }
+        mTmpDstFrame.offsetTo(0, 0);
 
         // Scale the mismatch dimensions to fill the task bounds
         mTmpSnapshotSize.set(0, 0, buffer.getWidth(), buffer.getHeight());
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 569b8f6..fba22dd 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2076,8 +2076,7 @@
      * @see #getAnimationAdapter
      */
     boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
-            boolean isVoiceInteraction,
-            @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
+            boolean isVoiceInteraction, @Nullable OnAnimationFinishedCallback finishedCallback) {
         if (mWmService.mDisableTransitionAnimation) {
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
                     "applyAnimation: transition animation is disabled or skipped. "
@@ -2092,22 +2091,7 @@
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation");
             if (okToAnimate()) {
-                final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
-                        transit, enter, isVoiceInteraction);
-                AnimationAdapter adapter = adapters.first;
-                AnimationAdapter thumbnailAdapter = adapters.second;
-                if (adapter != null) {
-                    startAnimation(getPendingTransaction(), adapter, !isVisible(),
-                            ANIMATION_TYPE_APP_TRANSITION, animationFinishedCallback);
-                    if (adapter.getShowWallpaper()) {
-                        getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
-                    }
-                    if (thumbnailAdapter != null) {
-                        mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(),
-                                thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION,
-                                (type, anim) -> { });
-                    }
-                }
+                applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, finishedCallback);
             } else {
                 cancelAnimation();
             }
@@ -2201,12 +2185,37 @@
         return resultAdapters;
     }
 
+    protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
+            int transit, boolean isVoiceInteraction,
+            @Nullable OnAnimationFinishedCallback finishedCallback) {
+        final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
+                transit, enter, isVoiceInteraction);
+        AnimationAdapter adapter = adapters.first;
+        AnimationAdapter thumbnailAdapter = adapters.second;
+        if (adapter != null) {
+            startAnimation(getPendingTransaction(), adapter, !isVisible(),
+                    ANIMATION_TYPE_APP_TRANSITION, finishedCallback);
+            if (adapter.getShowWallpaper()) {
+                getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+            }
+            if (thumbnailAdapter != null) {
+                mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(),
+                        thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> { });
+            }
+        }
+    }
+
     final SurfaceAnimationRunner getSurfaceAnimationRunner() {
         return mWmService.mSurfaceAnimationRunner;
     }
 
     private Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
                                     boolean isVoiceInteraction) {
+        if (isOrganized()) {
+            // Defer to the task organizer to run animations
+            return null;
+        }
+
         final DisplayContent displayContent = getDisplayContent();
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
         final int width = displayInfo.appWidth;
@@ -2295,6 +2304,10 @@
     public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
         mLastLayer = -1;
         reassignLayer(t);
+
+        // Leash is now responsible for position, so set our position to 0.
+        t.setPosition(mSurfaceControl, 0, 0);
+        mLastSurfacePosition.set(0, 0);
     }
 
     @Override
@@ -2302,6 +2315,7 @@
         mLastLayer = -1;
         mSurfaceFreezer.unfreeze(t);
         reassignLayer(t);
+        updateSurfacePosition(t);
     }
 
     /**
@@ -2365,11 +2379,15 @@
         }
     }
 
-    void updateSurfacePosition() {
+    final void updateSurfacePosition() {
+        updateSurfacePosition(getPendingTransaction());
+    }
+
+    void updateSurfacePosition(Transaction t) {
         // Avoid fighting with the organizer over Surface position.
         if (isOrganized()) return;
 
-        if (mSurfaceControl == null) {
+        if (mSurfaceControl == null || mSurfaceAnimator.hasLeash()) {
             return;
         }
 
@@ -2378,7 +2396,7 @@
             return;
         }
 
-        getPendingTransaction().setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
+        t.setPosition(mSurfaceControl, mTmpPos.x, mTmpPos.y);
         mLastSurfacePosition.set(mTmpPos.x, mTmpPos.y);
     }
 
@@ -2501,9 +2519,7 @@
             // We need to copy the SurfaceControl instead of returning the original
             // because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls
             // to release themselves.
-            SurfaceControl sc = new SurfaceControl();
-            sc.copyFrom(wc.getSurfaceControl());
-            return sc;
+            return new SurfaceControl(wc.getSurfaceControl());
         }
 
         WindowContainerToken toWindowContainerToken() {
@@ -2570,4 +2586,8 @@
 
         return willSync;
     }
+
+    boolean useBLASTSync() {
+        return mUsingBLASTSyncTransaction;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8d3a4f2..9d87976 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -25,6 +25,7 @@
 import static android.Manifest.permission.RESTRICTED_VR_ACCESS;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.StatusBarManager.DISABLE_MASK;
@@ -32,6 +33,7 @@
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_PC;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Process.myPid;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
@@ -72,6 +74,7 @@
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_BLAST_SYNC;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
 
@@ -1357,7 +1360,8 @@
             LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
             Rect outContentInsets, Rect outStableInsets,
             DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
-            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
+            InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
+            int requestUserId) {
         int[] appOp = new int[1];
         final boolean isRoundedCornerOverlay = (attrs.privateFlags
                 & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
@@ -1426,6 +1430,20 @@
                 return WindowManagerGlobal.ADD_INVALID_DISPLAY;
             }
 
+            int userId = UserHandle.getUserId(session.mUid);
+            if (requestUserId != userId) {
+                try {
+                    mAmInternal.handleIncomingUser(callingPid, callingUid, requestUserId,
+                            false /*allowAll*/, ALLOW_NON_FULL, null, null);
+                } catch (Exception exp) {
+                    ProtoLog.w(WM_ERROR, "Trying to add window with invalid user=%d",
+                            requestUserId);
+                    return WindowManagerGlobal.ADD_INVALID_USER;
+                }
+                // It's fine to use this userId
+                userId = requestUserId;
+            }
+
             ActivityRecord activity = null;
             final boolean hasParent = parentWindow != null;
             // Use existing parent window token for child windows since they go in the same token
@@ -1514,7 +1532,7 @@
             }
 
             final WindowState win = new WindowState(this, session, client, token, parentWindow,
-                    appOp[0], seq, attrs, viewVisibility, session.mUid,
+                    appOp[0], seq, attrs, viewVisibility, session.mUid, userId,
                     session.mCanAddInternalSystemWindow);
             if (win.mDeathRecipient == null) {
                 // Client has apparently died, so there is no reason to
@@ -1834,8 +1852,7 @@
         if ((w.mAttrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) {
             return true;
         }
-        if (DevicePolicyCache.getInstance().getScreenCaptureDisabled(
-                UserHandle.getUserId(w.mOwnerUid))) {
+        if (DevicePolicyCache.getInstance().getScreenCaptureDisabled(w.mShowUserId)) {
             return true;
         }
         return false;
@@ -2109,6 +2126,10 @@
                 win.finishSeamlessRotation(false /* timeout */);
             }
 
+            if (win.useBLASTSync()) {
+                result |= RELAYOUT_RES_BLAST_SYNC;
+            }
+
             int attrChanges = 0;
             int flagChanges = 0;
             int privateFlagChanges = 0;
@@ -2573,6 +2594,8 @@
             String packageName, boolean fromClientToken) {
         final boolean callerCanManageAppTokens =
                 checkCallingPermission(MANAGE_APP_TOKENS, "addWindowToken()");
+        // WindowContext users usually don't hold MANAGE_APP_TOKEN permission. Check permissions
+        // by checkAddPermission.
         if (!callerCanManageAppTokens) {
             final int res = mPolicy.checkAddPermission(type, false /* isRoundedCornerOverlay */,
                     packageName, new int[1]);
@@ -2587,7 +2610,7 @@
             synchronized (mGlobalLock) {
                 if (!callerCanManageAppTokens) {
                     if (packageName == null || !unprivilegedAppCanCreateTokenWith(
-                            null /* parentWindow */, callingUid, type, type, null /* tokenForLog */,
+                            null /* parentWindow */, callingUid, type, type, binder,
                             packageName)) {
                         throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
                     }
@@ -2612,7 +2635,7 @@
                     new WallpaperWindowToken(this, binder, true, dc, callerCanManageAppTokens);
                 } else {
                     new WindowToken(this, binder, type, true, dc, callerCanManageAppTokens,
-                            false /* roundedCornerOverlay */, fromClientToken);
+                            callingUid, false /* roundedCornerOverlay */, fromClientToken);
                 }
             }
         } finally {
@@ -2635,8 +2658,25 @@
 
     @Override
     public void removeWindowToken(IBinder binder, int displayId) {
-        if (!checkCallingPermission(MANAGE_APP_TOKENS, "removeWindowToken()")) {
-            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        final boolean callerCanManageAppTokens =
+                checkCallingPermission(MANAGE_APP_TOKENS, "removeWindowToken()");
+        final WindowToken windowToken;
+        synchronized (mGlobalLock) {
+            windowToken = mRoot.getWindowToken(binder);
+        }
+        if (windowToken == null) {
+            ProtoLog.w(WM_ERROR,
+                    "removeWindowToken: Attempted to remove non-existing token: %s", binder);
+            return;
+        }
+        final int callingUid = Binder.getCallingUid();
+
+        // If MANAGE_APP_TOKEN permission is not held(usually from WindowContext), callers can only
+        // remove the window tokens which they added themselves.
+        if (!callerCanManageAppTokens && (windowToken.getOwnerUid() == INVALID_UID
+                || callingUid != windowToken.getOwnerUid())) {
+            throw new SecurityException("removeWindowToken: Requires MANAGE_APP_TOKENS permission"
+                    + " to remove token owned by another uid");
         }
 
         final long origId = Binder.clearCallingIdentity();
@@ -2649,14 +2689,7 @@
                     return;
                 }
 
-                final WindowToken token = dc.removeWindowToken(binder);
-                if (token == null) {
-                    ProtoLog.w(WM_ERROR,
-                            "removeWindowToken: Attempted to remove non-existing token: %s",
-                            binder);
-                    return;
-                }
-
+                dc.removeWindowToken(binder);
                 dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
             }
         } finally {
@@ -7389,7 +7422,7 @@
             synchronized (mGlobalLock) {
                 WindowState window = mWindowMap.get(token);
                 if (window != null) {
-                    return UserHandle.getUserId(window.mOwnerUid);
+                    return window.mShowUserId;
                 }
                 return UserHandle.USER_NULL;
             }
@@ -8006,6 +8039,33 @@
         }
     }
 
+    /** Set layer tracing flags. */
+    public void setLayerTracingFlags(int flags) {
+        mAtmInternal.enforceCallerIsRecentsOrHasPermission(android.Manifest.permission.DUMP,
+                "setLayerTracingFlags");
+        long token = Binder.clearCallingIdentity();
+        try {
+            Parcel data = null;
+            try {
+                IBinder sf = ServiceManager.getService("SurfaceFlinger");
+                if (sf != null) {
+                    data = Parcel.obtain();
+                    data.writeInterfaceToken("android.ui.ISurfaceComposer");
+                    data.writeInt(flags);
+                    sf.transact(1033 /* LAYER_TRACE_FLAGS_CODE */, data, null, 0 /* flags */);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to set layer tracing flags");
+            } finally {
+                if (data != null) {
+                    data.recycle();
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     @Override
     public boolean mirrorDisplay(int displayId, SurfaceControl outSurfaceControl) {
         if (!checkCallingPermission(READ_FRAME_BUFFER, "mirrorDisplay()")) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d611ad8..c11c29b5 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -191,7 +191,6 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.Trace;
-import android.os.UserHandle;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -269,6 +268,12 @@
     final int mAppOp;
     // UserId and appId of the owner. Don't display windows of non-current user.
     final int mOwnerUid;
+    /**
+     * Requested userId, if this is not equals with the userId from mOwnerUid, then this window is
+     * created for secondary user.
+     * Use this member instead of get userId from mOwnerUid while query for visibility.
+     */
+    final int mShowUserId;
     /** The owner has {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW} */
     final boolean mOwnerCanAddInternalSystemWindow;
     final WindowId mWindowId;
@@ -806,8 +811,9 @@
 
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
             WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
-            int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
-        this(service, s, c, token, parentWindow, appOp, seq, a, viewVisibility, ownerId,
+            int viewVisibility, int ownerId, int showUserId,
+            boolean ownerCanAddInternalSystemWindow) {
+        this(service, s, c, token, parentWindow, appOp, seq, a, viewVisibility, ownerId, showUserId,
                 ownerCanAddInternalSystemWindow, new PowerManagerWrapper() {
                     @Override
                     public void wakeUp(long time, @WakeReason int reason, String details) {
@@ -823,8 +829,8 @@
 
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
             WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
-            int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow,
-            PowerManagerWrapper powerManagerWrapper) {
+            int viewVisibility, int ownerId, int showUserId,
+            boolean ownerCanAddInternalSystemWindow, PowerManagerWrapper powerManagerWrapper) {
         super(service);
         mSession = s;
         mClient = c;
@@ -832,6 +838,7 @@
         mToken = token;
         mActivityRecord = mToken.asActivityRecord();
         mOwnerUid = ownerId;
+        mShowUserId = showUserId;
         mOwnerCanAddInternalSystemWindow = ownerCanAddInternalSystemWindow;
         mWindowId = new WindowId(this);
         mAttrs.copyFrom(a);
@@ -3275,7 +3282,7 @@
         }
 
         return win.showForAllUsers()
-                || mWmService.isCurrentProfile(UserHandle.getUserId(win.mOwnerUid));
+                || mWmService.isCurrentProfile(win.mShowUserId);
     }
 
     private static void applyInsets(Region outRegion, Rect frame, Rect inset) {
@@ -3795,7 +3802,7 @@
     public void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         proto.write(HASH_CODE, System.identityHashCode(this));
-        proto.write(USER_ID, UserHandle.getUserId(mOwnerUid));
+        proto.write(USER_ID, mShowUserId);
         final CharSequence title = getWindowTag();
         if (title != null) {
             proto.write(TITLE, title.toString());
@@ -3979,7 +3986,7 @@
             mLastTitle = title;
             mWasExiting = mAnimatingExit;
             mStringNameCache = "Window{" + Integer.toHexString(System.identityHashCode(this))
-                    + " u" + UserHandle.getUserId(mOwnerUid)
+                    + " u" + mShowUserId
                     + " " + mLastTitle + (mAnimatingExit ? " EXITING}" : "}");
         }
         return mStringNameCache;
@@ -4274,9 +4281,12 @@
         logPerformShow("performShow on ");
 
         final int drawState = mWinAnimator.mDrawState;
-        if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW)
-                && mAttrs.type != TYPE_APPLICATION_STARTING && mActivityRecord != null) {
-            mActivityRecord.onFirstWindowDrawn(this, mWinAnimator);
+        if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {
+            if (mAttrs.type != TYPE_APPLICATION_STARTING) {
+                mActivityRecord.onFirstWindowDrawn(this, mWinAnimator);
+            } else {
+                mActivityRecord.onStartingWindowDrawn();
+            }
         }
 
         if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
@@ -5237,23 +5247,14 @@
     @Override
     public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
         super.onAnimationLeashCreated(t, leash);
-
-        // Leash is now responsible for position, so set our position to 0.
-        t.setPosition(mSurfaceControl, 0, 0);
-        mLastSurfacePosition.set(0, 0);
     }
 
     @Override
     public void onAnimationLeashLost(Transaction t) {
         super.onAnimationLeashLost(t);
-        updateSurfacePosition(t);
     }
 
     @Override
-    void updateSurfacePosition() {
-        updateSurfacePosition(getPendingTransaction());
-    }
-
     @VisibleForTesting
     void updateSurfacePosition(Transaction t) {
         if (mSurfaceControl == null) {
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 7457a1d..e34b816 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.os.Process.INVALID_UID;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
@@ -44,6 +45,7 @@
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
 import android.view.InsetsState;
@@ -106,6 +108,11 @@
     @VisibleForTesting
     final boolean mFromClientToken;
 
+    private DeathRecipient mDeathRecipient;
+    private boolean mBinderDied = false;
+
+    private final int mOwnerUid;
+
     /**
      * Used to fix the transform of the token to be rotated to a rotation different than it's
      * display. The window frames and surfaces corresponding to this token will be layouted and
@@ -165,6 +172,30 @@
         }
     }
 
+    private class DeathRecipient implements IBinder.DeathRecipient {
+        private boolean mHasUnlinkToDeath = false;
+
+        @Override
+        public void binderDied() {
+            synchronized (mWmService.mGlobalLock) {
+                mBinderDied = true;
+                removeImmediately();
+            }
+        }
+
+        void linkToDeath() throws RemoteException {
+            token.linkToDeath(DeathRecipient.this, 0);
+        }
+
+        void unlinkToDeath() {
+            if (mHasUnlinkToDeath) {
+                return;
+            }
+            token.unlinkToDeath(DeathRecipient.this, 0);
+            mHasUnlinkToDeath = true;
+        }
+    }
+
     /**
      * Compares two child window of this token and returns -1 if the first is lesser than the
      * second in terms of z-order and 1 otherwise.
@@ -193,23 +224,35 @@
 
     WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
             DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay) {
-        this(service, _token, type, persistOnEmpty, dc, ownerCanManageAppTokens,
+        this(service, _token, type, persistOnEmpty, dc, ownerCanManageAppTokens, INVALID_UID,
                 roundedCornerOverlay, false /* fromClientToken */);
     }
 
     WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
-            DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay,
-            boolean fromClientToken) {
+            DisplayContent dc, boolean ownerCanManageAppTokens, int ownerUid,
+            boolean roundedCornerOverlay, boolean fromClientToken) {
         super(service);
         token = _token;
         windowType = type;
         mPersistOnEmpty = persistOnEmpty;
         mOwnerCanManageAppTokens = ownerCanManageAppTokens;
+        mOwnerUid = ownerUid;
         mRoundedCornerOverlay = roundedCornerOverlay;
         mFromClientToken = fromClientToken;
         if (dc != null) {
             dc.addWindowToken(token, this);
         }
+        if (shouldReportToClient()) {
+            try {
+                mDeathRecipient = new DeathRecipient();
+                mDeathRecipient.linkToDeath();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Unable to add window token with type " + windowType + " on "
+                        + "display " + dc.getDisplayId(), e);
+                mDeathRecipient = null;
+                return;
+            }
+        }
     }
 
     void removeAllWindowsIfPossible() {
@@ -222,7 +265,7 @@
     }
 
     void setExiting() {
-        if (mChildren.size() == 0) {
+        if (isEmpty()) {
             super.removeImmediately();
             return;
         }
@@ -340,6 +383,21 @@
         // Needs to occur after the token is removed from the display above to avoid attempt at
         // duplicate removal of this window container from it's parent.
         super.removeImmediately();
+
+        reportWindowTokenRemovedToClient();
+    }
+
+    private void reportWindowTokenRemovedToClient() {
+        if (!shouldReportToClient()) {
+            return;
+        }
+        mDeathRecipient.unlinkToDeath();
+        IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(token);
+        try {
+            windowTokenClient.onWindowTokenRemoved();
+        } catch (RemoteException e) {
+            ProtoLog.w(WM_ERROR, "Could not report token removal to the window token client.");
+        }
     }
 
     @Override
@@ -361,17 +419,9 @@
     }
 
     void reportConfigToWindowTokenClient() {
-        if (asActivityRecord() != null) {
-            // Activities are updated through ATM callbacks.
+        if (!shouldReportToClient()) {
             return;
         }
-
-        // Unfortunately, this WindowToken is not from WindowContext so it cannot handle
-        // its own configuration changes.
-        if (!mFromClientToken) {
-            return;
-        }
-
         final Configuration config = getConfiguration();
         final int displayId = getDisplayContent().getDisplayId();
         if (config.equals(mLastReportedConfig) && displayId == mLastReportedDisplay) {
@@ -383,16 +433,26 @@
         mLastReportedDisplay = displayId;
 
         IWindowToken windowTokenClient = IWindowToken.Stub.asInterface(token);
-        if (windowTokenClient != null) {
-            try {
-                windowTokenClient.onConfigurationChanged(config, displayId);
-            } catch (RemoteException e) {
-                ProtoLog.w(WM_ERROR,
-                        "Could not report config changes to the window token client.");
-            }
+        try {
+            windowTokenClient.onConfigurationChanged(config, displayId);
+        } catch (RemoteException e) {
+            ProtoLog.w(WM_ERROR,
+                    "Could not report config changes to the window token client.");
         }
     }
 
+    /**
+     * @return {@code true} if this {@link WindowToken} is not an {@link ActivityRecord} and
+     * registered from client side.
+     */
+    private boolean shouldReportToClient() {
+        // Only report to client for WindowToken because Activities are updated through ATM
+        // callbacks.
+        return asActivityRecord() == null
+        // Report to {@link android.view.WindowTokenClient} if this token was registered from it.
+                && mFromClientToken && !mBinderDied;
+    }
+
     @Override
     void assignLayer(SurfaceControl.Transaction t, int layer) {
         if (windowType == TYPE_DOCK_DIVIDER) {
@@ -494,13 +554,12 @@
         // cleared and the configuration is restored from parent.
         if (!changed) {
             clearFixedRotationTransform(null /* applyDisplayRotation */);
-            onConfigurationChanged(getParent().getConfiguration());
         }
     }
 
     /**
-     * Clears the transform and apply display rotation if the action is given. The caller needs to
-     * refresh the configuration of this container after this method call.
+     * Clears the transform and apply display rotation if the action is given. If the display will
+     * not rotate, the transformed containers are restored to their original states.
      */
     void clearFixedRotationTransform(Runnable applyDisplayRotation) {
         final FixedRotationTransformState state = mFixedRotationTransformState;
@@ -514,6 +573,12 @@
         state.mIsTransforming = false;
         if (applyDisplayRotation != null) {
             applyDisplayRotation.run();
+        } else {
+            // The display will not rotate to the rotation of this container, let's cancel them.
+            for (int i = state.mAssociatedTokens.size() - 1; i >= 0; i--) {
+                state.mAssociatedTokens.get(i).cancelFixedRotationTransform();
+            }
+            cancelFixedRotationTransform();
         }
         // The state is cleared at the end, because it is used to indicate that other windows can
         // use seamless rotation when applying rotation to display.
@@ -523,6 +588,16 @@
         mFixedRotationTransformState = null;
     }
 
+    /** Restores the changes that applies to this container. */
+    private void cancelFixedRotationTransform() {
+        final WindowContainer<?> parent = getParent();
+        if (parent == null) {
+            // The window may be detached or detaching.
+            return;
+        }
+        onConfigurationChanged(parent.getConfiguration());
+    }
+
     @Override
     void resolveOverrideConfiguration(Configuration newParentConfig) {
         super.resolveOverrideConfiguration(newParentConfig);
@@ -535,8 +610,8 @@
     }
 
     @Override
-    void updateSurfacePosition() {
-        super.updateSurfacePosition();
+    void updateSurfacePosition(SurfaceControl.Transaction t) {
+        super.updateSurfacePosition(t);
         if (isFixedRotationTransforming()) {
             // The window is layouted in a simulated rotated display but the real display hasn't
             // rotated, so here transforms its surface to fit in the real display.
@@ -616,4 +691,8 @@
     int getWindowLayerFromType() {
         return mWmService.mPolicy.getWindowLayerFromTypeLw(windowType, mOwnerCanManageAppTokens);
     }
+
+    int getOwnerUid() {
+        return mOwnerUid;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 67e83ba..1da0740 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1836,8 +1836,9 @@
         Bundle addSyntheticRestrictions(Bundle restrictions) {
             if (disableCamera) {
                 restrictions.putBoolean(UserManager.DISALLOW_CAMERA, true);
-            } else {
-                restrictions.remove(UserManager.DISALLOW_CAMERA);
+            }
+            if (requireAutoTime) {
+                restrictions.putBoolean(UserManager.DISALLOW_CONFIG_DATE_TIME, true);
             }
             return restrictions;
         }
@@ -1864,7 +1865,7 @@
 
         Bundle getEffectiveRestrictions() {
             return addSyntheticRestrictions(
-                    removeDeprecatedRestrictions(ensureUserRestrictions()));
+                    removeDeprecatedRestrictions(new Bundle(ensureUserRestrictions())));
         }
 
         Bundle getLocalUserRestrictions(int adminType) {
@@ -2701,10 +2702,8 @@
         Slog.i(LOG_TAG, "Clearing the DO...");
         final ComponentName doAdminReceiver = doAdmin.info.getComponent();
         clearDeviceOwnerLocked(doAdmin, doUserId);
-        // TODO(b/143516163): If we have a power cut here, we might leave active admin. Consider if
-        // it is worth the complexity to make it more robust.
         Slog.i(LOG_TAG, "Removing admin artifacts...");
-        // TODO(b/143516163): Clean up application restrictions in UserManager.
+        // TODO(b/149075700): Clean up application restrictions in UserManager.
         removeAdminArtifacts(doAdminReceiver, doUserId);
         Slog.i(LOG_TAG, "Migration complete.");
 
@@ -2746,16 +2745,12 @@
 
         // The following policies weren't available to PO, but will be available after migration.
         parentAdmin.disableCamera = doAdmin.disableCamera;
-
-        // TODO(b/143516163): Uncomment once corresponding APIs are available via parent instance.
-        // parentAdmin.disableScreenCapture = doAdmin.disableScreenCapture;
-        // parentAdmin.accountTypesWithManagementDisabled.addAll(
-        //         doAdmin.accountTypesWithManagementDisabled);
+        parentAdmin.requireAutoTime = doAdmin.requireAutoTime;
+        parentAdmin.disableScreenCapture = doAdmin.disableScreenCapture;
+        parentAdmin.accountTypesWithManagementDisabled.addAll(
+                doAdmin.accountTypesWithManagementDisabled);
 
         moveDoUserRestrictionsToCopeParent(doAdmin, parentAdmin);
-
-        // TODO(b/143516163): migrate network and security logging state, currently they are
-        // turned off when DO is removed.
     }
 
     private void moveDoUserRestrictionsToCopeParent(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
@@ -2775,7 +2770,7 @@
      * a managed profile.
      */
     @GuardedBy("getLockObject()")
-    void applyManagedProfileRestrictionIfDeviceOwnerLocked() {
+    private void applyManagedProfileRestrictionIfDeviceOwnerLocked() {
         final int doUserId = mOwners.getDeviceOwnerUserId();
         if (doUserId == UserHandle.USER_NULL) {
             logIfVerbose("No DO found, skipping application of restriction.");
@@ -3999,11 +3994,11 @@
                 mOwners.systemReady();
                 break;
             case SystemService.PHASE_ACTIVITY_MANAGER_READY:
-                maybeStartSecurityLogMonitorOnActivityManagerReady();
                 synchronized (getLockObject()) {
                     migrateToProfileOnOrganizationOwnedDeviceIfCompLocked();
                     applyManagedProfileRestrictionIfDeviceOwnerLocked();
                 }
+                maybeStartSecurityLogMonitorOnActivityManagerReady();
                 final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
                 if (userId >= 0) {
                     updatePersonalAppSuspension(userId, false /* running */);
@@ -7839,16 +7834,21 @@
         }
         Objects.requireNonNull(who, "ComponentName is null");
         final int userHandle = UserHandle.getCallingUserId();
+        boolean requireAutoTimeChanged = false;
         synchronized (getLockObject()) {
             ActiveAdmin admin = getActiveAdminForCallerLocked(who,
                     DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
             if (admin.requireAutoTime != required) {
                 admin.requireAutoTime = required;
                 saveSettingsLocked(userHandle);
+                requireAutoTimeChanged = true;
             }
         }
-
-        // TODO: (b/145604635) Add upgrade case
+        // requireAutoTime is now backed by DISALLOW_CONFIG_DATE_TIME restriction, so propagate
+        // updated restrictions to the framework.
+        if (requireAutoTimeChanged) {
+            pushUserRestrictions(userHandle);
+        }
         // Turn AUTO_TIME on in settings if it is required
         if (required) {
             mInjector.binderWithCleanCallingIdentity(
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 97de1800..aabc58c 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -17,6 +17,7 @@
 #include "BinderIncrementalService.h"
 
 #include <android-base/logging.h>
+#include <android-base/no_destructor.h>
 #include <binder/IResultReceiver.h>
 #include <binder/PermissionCache.h>
 #include <incfs.h>
@@ -93,8 +94,8 @@
 }
 
 status_t BinderIncrementalService::dump(int fd, const Vector<String16>&) {
-    static const String16 kDump("android.permission.DUMP");
-    if (!PermissionCache::checkCallingPermission(kDump)) {
+    static const android::base::NoDestructor<String16> kDump("android.permission.DUMP");
+    if (!PermissionCache::checkCallingPermission(*kDump)) {
         return PERMISSION_DENIED;
     }
     mImpl.onDump(fd);
@@ -155,11 +156,6 @@
     return ok();
 }
 
-binder::Status BinderIncrementalService::setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) {
-    *_aidl_return = mImpl.setStorageParams(storage, enableReadLogs);
-    return ok();
-}
-
 binder::Status BinderIncrementalService::makeDirectory(int32_t storageId, const std::string& path,
                                                        int32_t* _aidl_return) {
     *_aidl_return = mImpl.makeDir(storageId, path);
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index d0357d9..28613e1 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -71,7 +71,6 @@
     binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath,
                                            const std::string& libDirRelativePath,
                                            const std::string& abi, bool* _aidl_return) final;
-    binder::Status setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) final;
 
 private:
     android::incremental::IncrementalService mImpl;
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index de24bcf..eb65a2d 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -20,6 +20,7 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/no_destructor.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
@@ -51,7 +52,7 @@
 namespace fs = std::filesystem;
 
 constexpr const char* kDataUsageStats = "android.permission.LOADER_USAGE_STATS";
-constexpr const char* kOpUsage = "android:get_usage_stats";
+constexpr const char* kOpUsage = "android:loader_usage_stats";
 
 namespace android::incremental {
 
@@ -163,7 +164,9 @@
         android::base::GetBoolProperty("incremental.perflogging", false);
 
 IncrementalService::IncFsMount::~IncFsMount() {
-    incrementalService.mDataLoaderManager->destroyDataLoader(mountId);
+    if (dataLoaderStub) {
+        dataLoaderStub->destroy();
+    }
     LOG(INFO) << "Unmounting and cleaning up mount " << mountId << " with root '" << root << '\'';
     for (auto&& [target, _] : bindPoints) {
         LOG(INFO) << "\tbind: " << target;
@@ -288,9 +291,12 @@
         dprintf(fd, "\t\tmountId: %d\n", mnt.mountId);
         dprintf(fd, "\t\troot: %s\n", mnt.root.c_str());
         dprintf(fd, "\t\tnextStorageDirNo: %d\n", mnt.nextStorageDirNo.load());
-        dprintf(fd, "\t\tdataLoaderStatus: %d\n", mnt.dataLoaderStatus.load());
-        {
-            const auto& params = mnt.dataLoaderParams;
+        if (mnt.dataLoaderStub) {
+            const auto& dataLoaderStub = *mnt.dataLoaderStub;
+            dprintf(fd, "\t\tdataLoaderStatus: %d\n", dataLoaderStub.status());
+            dprintf(fd, "\t\tdataLoaderStartRequested: %s\n",
+                    dataLoaderStub.startRequested() ? "true" : "false");
+            const auto& params = dataLoaderStub.params();
             dprintf(fd, "\t\tdataLoaderParams:\n");
             dprintf(fd, "\t\t\ttype: %s\n", toString(params.type).c_str());
             dprintf(fd, "\t\t\tpackageName: %s\n", params.packageName.c_str());
@@ -321,10 +327,9 @@
     }
 }
 
-std::optional<std::future<void>> IncrementalService::onSystemReady() {
-    std::promise<void> threadFinished;
+void IncrementalService::onSystemReady() {
     if (mSystemReady.exchange(true)) {
-        return {};
+        return;
     }
 
     std::vector<IfsMountPtr> mounts;
@@ -338,8 +343,8 @@
         }
     }
 
+    /* TODO(b/151241369): restore data loaders on reboot.
     std::thread([this, mounts = std::move(mounts)]() {
-        /* TODO(b/151241369): restore data loaders on reboot.
         for (auto&& ifs : mounts) {
             if (prepareDataLoader(*ifs)) {
                 LOG(INFO) << "Successfully started data loader for mount " << ifs->mountId;
@@ -348,10 +353,8 @@
                 LOG(WARNING) << "Failed to start data loader for mount " << ifs->mountId;
             }
         }
-        */
-        mPrepareDataLoaders.set_value_at_thread_exit();
     }).detach();
-    return mPrepareDataLoaders.get_future();
+    */
 }
 
 auto IncrementalService::getStorageSlotLocked() -> MountMap::iterator {
@@ -468,15 +471,13 @@
         return kInvalidStorageId;
     }
 
-    ifs->dataLoaderParams = std::move(dataLoaderParams);
-
     {
         metadata::Mount m;
         m.mutable_storage()->set_id(ifs->mountId);
-        m.mutable_loader()->set_type((int)ifs->dataLoaderParams.type);
-        m.mutable_loader()->set_package_name(ifs->dataLoaderParams.packageName);
-        m.mutable_loader()->set_class_name(ifs->dataLoaderParams.className);
-        m.mutable_loader()->set_arguments(ifs->dataLoaderParams.arguments);
+        m.mutable_loader()->set_type((int)dataLoaderParams.type);
+        m.mutable_loader()->set_package_name(dataLoaderParams.packageName);
+        m.mutable_loader()->set_class_name(dataLoaderParams.className);
+        m.mutable_loader()->set_arguments(dataLoaderParams.arguments);
         const auto metadata = m.SerializeAsString();
         m.mutable_loader()->release_arguments();
         m.mutable_loader()->release_class_name();
@@ -504,14 +505,20 @@
     // Done here as well, all data structures are in good state.
     secondCleanupOnFailure.release();
 
-    if (!prepareDataLoader(*ifs, &dataLoaderStatusListener)) {
-        LOG(ERROR) << "prepareDataLoader() failed";
-        deleteStorageLocked(*ifs, std::move(l));
-        return kInvalidStorageId;
-    }
+    auto dataLoaderStub =
+            prepareDataLoader(*ifs, std::move(dataLoaderParams), &dataLoaderStatusListener);
+    CHECK(dataLoaderStub);
 
     mountIt->second = std::move(ifs);
     l.unlock();
+
+    if (mSystemReady.load(std::memory_order_relaxed) && !dataLoaderStub->create()) {
+        // failed to create data loader
+        LOG(ERROR) << "initializeDataLoader() failed";
+        deleteStorage(dataLoaderStub->id());
+        return kInvalidStorageId;
+    }
+
     LOG(INFO) << "created storage " << mountId;
     return mountId;
 }
@@ -581,13 +588,14 @@
 int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) {
     const auto ifs = getIfs(storageId);
     if (!ifs) {
+        LOG(ERROR) << "setStorageParams failed, invalid storageId: " << storageId;
         return -EINVAL;
     }
 
+    const auto& params = ifs->dataLoaderStub->params();
     if (enableReadLogs) {
-        if (auto status =
-                    mAppOpsManager->checkPermission(kDataUsageStats, kOpUsage,
-                                                    ifs->dataLoaderParams.packageName.c_str());
+        if (auto status = mAppOpsManager->checkPermission(kDataUsageStats, kOpUsage,
+                                                          params.packageName.c_str());
             !status.isOk()) {
             LOG(ERROR) << "checkPermission failed: " << status.toString8();
             return fromBinderStatus(status);
@@ -600,7 +608,7 @@
     }
 
     if (enableReadLogs) {
-        registerAppOpsCallback(ifs->dataLoaderParams.packageName);
+        registerAppOpsCallback(params.packageName);
     }
 
     return 0;
@@ -700,8 +708,8 @@
 const IncrementalService::IfsMountPtr& IncrementalService::getIfsLocked(StorageId storage) const {
     auto it = mMounts.find(storage);
     if (it == mMounts.end()) {
-        static const IfsMountPtr kEmpty = {};
-        return kEmpty;
+        static const android::base::NoDestructor<IfsMountPtr> kEmpty{};
+        return *kEmpty;
     }
     return it->second;
 }
@@ -983,34 +991,19 @@
 }
 
 bool IncrementalService::startLoading(StorageId storage) const {
+    DataLoaderStubPtr dataLoaderStub;
     {
         std::unique_lock l(mLock);
         const auto& ifs = getIfsLocked(storage);
         if (!ifs) {
             return false;
         }
-        if (ifs->dataLoaderStatus != IDataLoaderStatusListener::DATA_LOADER_CREATED) {
-            ifs->dataLoaderStartRequested = true;
-            return true;
+        dataLoaderStub = ifs->dataLoaderStub;
+        if (!dataLoaderStub) {
+            return false;
         }
     }
-    return startDataLoader(storage);
-}
-
-bool IncrementalService::startDataLoader(MountId mountId) const {
-    sp<IDataLoader> dataloader;
-    auto status = mDataLoaderManager->getDataLoader(mountId, &dataloader);
-    if (!status.isOk()) {
-        return false;
-    }
-    if (!dataloader) {
-        return false;
-    }
-    status = dataloader->start(mountId);
-    if (!status.isOk()) {
-        return false;
-    }
-    return true;
+    return dataLoaderStub->start();
 }
 
 void IncrementalService::mountExistingImages() {
@@ -1056,13 +1049,13 @@
     mNextId = std::max(mNextId, ifs->mountId + 1);
 
     // DataLoader params
+    DataLoaderParamsParcel dataLoaderParams;
     {
-        auto& dlp = ifs->dataLoaderParams;
         const auto& loader = mount.loader();
-        dlp.type = (android::content::pm::DataLoaderType)loader.type();
-        dlp.packageName = loader.package_name();
-        dlp.className = loader.class_name();
-        dlp.arguments = loader.arguments();
+        dataLoaderParams.type = (android::content::pm::DataLoaderType)loader.type();
+        dataLoaderParams.packageName = loader.package_name();
+        dataLoaderParams.className = loader.class_name();
+        dataLoaderParams.arguments = loader.arguments();
     }
 
     std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints;
@@ -1134,17 +1127,13 @@
     return true;
 }
 
-bool IncrementalService::prepareDataLoader(IncrementalService::IncFsMount& ifs,
-                                           const DataLoaderStatusListener* externalListener) {
-    if (!mSystemReady.load(std::memory_order_relaxed)) {
-        std::unique_lock l(ifs.lock);
-        return true; // eventually...
-    }
-
+IncrementalService::DataLoaderStubPtr IncrementalService::prepareDataLoader(
+        IncrementalService::IncFsMount& ifs, DataLoaderParamsParcel&& params,
+        const DataLoaderStatusListener* externalListener) {
     std::unique_lock l(ifs.lock);
-    if (ifs.dataLoaderStatus != -1) {
+    if (ifs.dataLoaderStub) {
         LOG(INFO) << "Skipped data loader preparation because it already exists";
-        return true;
+        return ifs.dataLoaderStub;
     }
 
     FileSystemControlParcel fsControlParcel;
@@ -1153,17 +1142,11 @@
     fsControlParcel.incremental->pendingReads.reset(
             base::unique_fd(::dup(ifs.control.pendingReads())));
     fsControlParcel.incremental->log.reset(base::unique_fd(::dup(ifs.control.logs())));
-    sp<IncrementalDataLoaderListener> listener =
-            new IncrementalDataLoaderListener(*this,
-                                              externalListener ? *externalListener
-                                                               : DataLoaderStatusListener());
-    bool created = false;
-    auto status = mDataLoaderManager->initializeDataLoader(ifs.mountId, ifs.dataLoaderParams, fsControlParcel, listener, &created);
-    if (!status.isOk() || !created) {
-        LOG(ERROR) << "Failed to create a data loader for mount " << ifs.mountId;
-        return false;
-    }
-    return true;
+    fsControlParcel.service = new IncrementalServiceConnector(*this, ifs.mountId);
+
+    ifs.dataLoaderStub = new DataLoaderStub(*this, ifs.mountId, std::move(params),
+                                            std::move(fsControlParcel), externalListener);
+    return ifs.dataLoaderStub;
 }
 
 template <class Duration>
@@ -1374,7 +1357,7 @@
         std::lock_guard l(mLock);
         affected.reserve(mMounts.size());
         for (auto&& [id, ifs] : mMounts) {
-            if (ifs->mountId == id && ifs->dataLoaderParams.packageName == packageName) {
+            if (ifs->mountId == id && ifs->dataLoaderStub->params().packageName == packageName) {
                 affected.push_back(ifs);
             }
         }
@@ -1384,37 +1367,79 @@
     }
 }
 
-binder::Status IncrementalService::IncrementalDataLoaderListener::onStatusChanged(MountId mountId,
-                                                                                  int newStatus) {
-    if (externalListener) {
-        // Give an external listener a chance to act before we destroy something.
-        externalListener->onStatusChanged(mountId, newStatus);
+IncrementalService::DataLoaderStub::~DataLoaderStub() {
+    CHECK(mStatus == -1 || mStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED)
+            << "Dataloader has to be destroyed prior to destructor: " << mId
+            << ", status: " << mStatus;
+}
+
+bool IncrementalService::DataLoaderStub::create() {
+    bool created = false;
+    auto status = mService.mDataLoaderManager->initializeDataLoader(mId, mParams, mControl, this,
+                                                                    &created);
+    if (!status.isOk() || !created) {
+        LOG(ERROR) << "Failed to create a data loader for mount " << mId;
+        return false;
+    }
+    return true;
+}
+
+bool IncrementalService::DataLoaderStub::start() {
+    if (mStatus != IDataLoaderStatusListener::DATA_LOADER_CREATED) {
+        mStartRequested = true;
+        return true;
     }
 
-    bool startRequested = false;
+    sp<IDataLoader> dataloader;
+    auto status = mService.mDataLoaderManager->getDataLoader(mId, &dataloader);
+    if (!status.isOk()) {
+        return false;
+    }
+    if (!dataloader) {
+        return false;
+    }
+    status = dataloader->start(mId);
+    if (!status.isOk()) {
+        return false;
+    }
+    return true;
+}
+
+void IncrementalService::DataLoaderStub::destroy() {
+    mDestroyRequested = true;
+    mService.mDataLoaderManager->destroyDataLoader(mId);
+}
+
+binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mountId, int newStatus) {
+    if (mStatus == newStatus) {
+        return binder::Status::ok();
+    }
+
+    if (mListener) {
+        // Give an external listener a chance to act before we destroy something.
+        mListener->onStatusChanged(mountId, newStatus);
+    }
+
     {
-        std::unique_lock l(incrementalService.mLock);
-        const auto& ifs = incrementalService.getIfsLocked(mountId);
+        std::unique_lock l(mService.mLock);
+        const auto& ifs = mService.getIfsLocked(mountId);
         if (!ifs) {
             LOG(WARNING) << "Received data loader status " << int(newStatus)
                          << " for unknown mount " << mountId;
             return binder::Status::ok();
         }
-        ifs->dataLoaderStatus = newStatus;
+        mStatus = newStatus;
 
-        if (newStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED) {
-            ifs->dataLoaderStatus = IDataLoaderStatusListener::DATA_LOADER_STOPPED;
-            incrementalService.deleteStorageLocked(*ifs, std::move(l));
+        if (!mDestroyRequested && newStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED) {
+            mService.deleteStorageLocked(*ifs, std::move(l));
             return binder::Status::ok();
         }
-
-        startRequested = ifs->dataLoaderStartRequested;
     }
 
     switch (newStatus) {
         case IDataLoaderStatusListener::DATA_LOADER_CREATED: {
-            if (startRequested) {
-                incrementalService.startDataLoader(mountId);
+            if (mStartRequested) {
+                start();
             }
             break;
         }
@@ -1451,4 +1476,10 @@
     incrementalService.onAppOpChanged(packageName);
 }
 
+binder::Status IncrementalService::IncrementalServiceConnector::setStorageParams(
+        bool enableReadLogs, int32_t* _aidl_return) {
+    *_aidl_return = incrementalService.setStorageParams(storage, enableReadLogs);
+    return binder::Status::ok();
+}
+
 } // namespace android::incremental
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 9b15646..27d40f1 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -39,6 +39,7 @@
 
 #include "ServiceWrappers.h"
 #include "android/content/pm/BnDataLoaderStatusListener.h"
+#include "android/os/incremental/BnIncrementalServiceConnector.h"
 #include "incfs.h"
 #include "path.h"
 
@@ -59,7 +60,8 @@
 using TimePoint = std::chrono::time_point<Clock>;
 using Seconds = std::chrono::seconds;
 
-using DataLoaderStatusListener = ::android::sp<::android::content::pm::IDataLoaderStatusListener>;
+using IDataLoaderStatusListener = ::android::content::pm::IDataLoaderStatusListener;
+using DataLoaderStatusListener = ::android::sp<IDataLoaderStatusListener>;
 
 class IncrementalService final {
 public:
@@ -94,7 +96,7 @@
 
     void onDump(int fd);
 
-    std::optional<std::future<void>> onSystemReady();
+    void onSystemReady();
 
     StorageId createStorage(std::string_view mountPoint, DataLoaderParamsParcel&& dataLoaderParams,
                             const DataLoaderStatusListener& dataLoaderStatusListener,
@@ -133,32 +135,69 @@
     bool configureNativeBinaries(StorageId storage, std::string_view apkFullPath,
                                  std::string_view libDirRelativePath, std::string_view abi);
 
-    class IncrementalDataLoaderListener : public android::content::pm::BnDataLoaderStatusListener {
-    public:
-        IncrementalDataLoaderListener(IncrementalService& incrementalService,
-                                      DataLoaderStatusListener externalListener)
-              : incrementalService(incrementalService), externalListener(externalListener) {}
-        // Callbacks interface
-        binder::Status onStatusChanged(MountId mount, int newStatus) override;
-
-    private:
-        IncrementalService& incrementalService;
-        DataLoaderStatusListener externalListener;
-    };
-
     class AppOpsListener : public android::BnAppOpsCallback {
     public:
         AppOpsListener(IncrementalService& incrementalService, std::string packageName) : incrementalService(incrementalService), packageName(std::move(packageName)) {}
-        void opChanged(int32_t op, const String16& packageName) override;
+        void opChanged(int32_t op, const String16& packageName) final;
 
     private:
         IncrementalService& incrementalService;
         const std::string packageName;
     };
 
+    class IncrementalServiceConnector : public BnIncrementalServiceConnector {
+    public:
+        IncrementalServiceConnector(IncrementalService& incrementalService, int32_t storage)
+              : incrementalService(incrementalService), storage(storage) {}
+        binder::Status setStorageParams(bool enableReadLogs, int32_t* _aidl_return) final;
+
+    private:
+        IncrementalService& incrementalService;
+        int32_t const storage;
+    };
+
 private:
     static const bool sEnablePerfLogging;
 
+    struct IncFsMount;
+
+    class DataLoaderStub : public android::content::pm::BnDataLoaderStatusListener {
+    public:
+        DataLoaderStub(IncrementalService& service, MountId id, DataLoaderParamsParcel&& params,
+                       FileSystemControlParcel&& control,
+                       const DataLoaderStatusListener* externalListener)
+              : mService(service),
+                mId(id),
+                mParams(std::move(params)),
+                mControl(std::move(control)),
+                mListener(externalListener ? *externalListener : DataLoaderStatusListener()) {}
+        ~DataLoaderStub();
+
+        bool create();
+        bool start();
+        void destroy();
+
+        // accessors
+        MountId id() const { return mId; }
+        const DataLoaderParamsParcel& params() const { return mParams; }
+        int status() const { return mStatus.load(); }
+        bool startRequested() const { return mStartRequested; }
+
+    private:
+        binder::Status onStatusChanged(MountId mount, int newStatus) final;
+
+        IncrementalService& mService;
+        MountId const mId;
+        DataLoaderParamsParcel const mParams;
+        FileSystemControlParcel const mControl;
+        DataLoaderStatusListener const mListener;
+
+        std::atomic<int> mStatus = -1;
+        bool mStartRequested = false;
+        bool mDestroyRequested = false;
+    };
+    using DataLoaderStubPtr = sp<DataLoaderStub>;
+
     struct IncFsMount {
         struct Bind {
             StorageId storage;
@@ -182,10 +221,8 @@
         /*const*/ MountId mountId;
         StorageMap storages;
         BindMap bindPoints;
-        DataLoaderParamsParcel dataLoaderParams;
+        DataLoaderStubPtr dataLoaderStub;
         std::atomic<int> nextStorageDirNo{0};
-        std::atomic<int> dataLoaderStatus = -1;
-        bool dataLoaderStartRequested = false;
         const IncrementalService& incrementalService;
 
         IncFsMount(std::string root, MountId mountId, Control control,
@@ -220,8 +257,8 @@
                            std::string&& source, std::string&& target, BindKind kind,
                            std::unique_lock<std::mutex>& mainLock);
 
-    bool prepareDataLoader(IncFsMount& ifs, const DataLoaderStatusListener* externalListener = nullptr);
-    bool startDataLoader(MountId mountId) const;
+    DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs, DataLoaderParamsParcel&& params,
+                                        const DataLoaderStatusListener* externalListener = nullptr);
 
     BindPathMap::const_iterator findStorageLocked(std::string_view path) const;
     StorageId findStorageId(std::string_view path) const;
@@ -257,7 +294,6 @@
 
     std::atomic_bool mSystemReady = false;
     StorageId mNextId = 0;
-    std::promise<void> mPrepareDataLoaders;
 };
 
 } // namespace android::incremental
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 0635ae1..9911319 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -131,44 +131,60 @@
                        binder::Status(int32_t mountId, sp<IDataLoader>* _aidl_return));
     MOCK_CONST_METHOD1(destroyDataLoader, binder::Status(int32_t mountId));
 
+    void initializeDataLoaderSuccess() {
+        ON_CALL(*this, initializeDataLoader(_, _, _, _, _))
+                .WillByDefault(Invoke(this, &MockDataLoaderManager::initializeDataLoaderOk));
+    }
+    void initializeDataLoaderFails() {
+        ON_CALL(*this, initializeDataLoader(_, _, _, _, _))
+                .WillByDefault(Return(
+                        (binder::Status::fromExceptionCode(1, String8("failed to prepare")))));
+    }
+    void getDataLoaderSuccess() {
+        ON_CALL(*this, getDataLoader(_, _))
+                .WillByDefault(Invoke(this, &MockDataLoaderManager::getDataLoaderOk));
+    }
+    void destroyDataLoaderOk() {
+        ON_CALL(*this, destroyDataLoader(_))
+                .WillByDefault(Invoke(this, &MockDataLoaderManager::setDataLoaderStatusDestroyed));
+    }
     binder::Status initializeDataLoaderOk(int32_t mountId, const DataLoaderParamsParcel& params,
                                           const FileSystemControlParcel& control,
                                           const sp<IDataLoaderStatusListener>& listener,
                                           bool* _aidl_return) {
         mId = mountId;
         mListener = listener;
+        mServiceConnector = control.service;
         *_aidl_return = true;
         return binder::Status::ok();
     }
-
     binder::Status getDataLoaderOk(int32_t mountId, sp<IDataLoader>* _aidl_return) {
         *_aidl_return = mDataLoader;
         return binder::Status::ok();
     }
-
-    void initializeDataLoaderFails() {
-        ON_CALL(*this, initializeDataLoader(_, _, _, _, _))
-                .WillByDefault(Return(
-                        (binder::Status::fromExceptionCode(1, String8("failed to prepare")))));
-    }
-    void initializeDataLoaderSuccess() {
-        ON_CALL(*this, initializeDataLoader(_, _, _, _, _))
-                .WillByDefault(Invoke(this, &MockDataLoaderManager::initializeDataLoaderOk));
-    }
-    void getDataLoaderSuccess() {
-        ON_CALL(*this, getDataLoader(_, _))
-                .WillByDefault(Invoke(this, &MockDataLoaderManager::getDataLoaderOk));
-    }
     void setDataLoaderStatusNotReady() {
         mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
     }
     void setDataLoaderStatusReady() {
         mListener->onStatusChanged(mId, IDataLoaderStatusListener::DATA_LOADER_CREATED);
     }
+    binder::Status setDataLoaderStatusDestroyed(int32_t id) {
+        if (mListener) {
+            mListener->onStatusChanged(id, IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
+        }
+        return binder::Status::ok();
+    }
+    int32_t setStorageParams(bool enableReadLogs) {
+        int32_t result = -1;
+        EXPECT_NE(mServiceConnector.get(), nullptr);
+        EXPECT_TRUE(mServiceConnector->setStorageParams(enableReadLogs, &result).isOk());
+        return result;
+    }
 
 private:
     int mId;
     sp<IDataLoaderStatusListener> mListener;
+    sp<IIncrementalServiceConnector> mServiceConnector;
     sp<IDataLoader> mDataLoader = sp<IDataLoader>(new FakeDataLoader());
 };
 
@@ -290,6 +306,7 @@
                                                      mRootDir.path);
         mDataLoaderParcel.packageName = "com.test";
         mDataLoaderParcel.arguments = "uri";
+        mDataLoaderManager->destroyDataLoaderOk();
         mIncrementalService->onSystemReady();
     }
 
@@ -337,6 +354,7 @@
 TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) {
     mVold->mountIncFsInvalidControlParcel();
     EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(0);
+    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(0);
     TemporaryDir tempDir;
     int storageId =
             mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
@@ -348,7 +366,7 @@
     mVold->mountIncFsSuccess();
     mIncFs->makeFileFails();
     EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(0);
-    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_));
+    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(0);
     EXPECT_CALL(*mVold, unmountIncFs(_));
     TemporaryDir tempDir;
     int storageId =
@@ -362,7 +380,7 @@
     mIncFs->makeFileSuccess();
     mVold->bindMountFails();
     EXPECT_CALL(*mDataLoaderManager, initializeDataLoader(_, _, _, _, _)).Times(0);
-    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_));
+    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(0);
     EXPECT_CALL(*mVold, unmountIncFs(_));
     TemporaryDir tempDir;
     int storageId =
@@ -376,7 +394,7 @@
     mIncFs->makeFileSuccess();
     mVold->bindMountSuccess();
     mDataLoaderManager->initializeDataLoaderFails();
-    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_));
+    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(1);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
     int storageId =
@@ -390,7 +408,7 @@
     mIncFs->makeFileSuccess();
     mVold->bindMountSuccess();
     mDataLoaderManager->initializeDataLoaderSuccess();
-    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_));
+    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_)).Times(1);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     TemporaryDir tempDir;
     int storageId =
@@ -453,7 +471,7 @@
             mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
                                                IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
-    ASSERT_GE(mIncrementalService->setStorageParams(storageId, true), 0);
+    ASSERT_GE(mDataLoaderManager->setStorageParams(true), 0);
 }
 
 TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndPermissionChanged) {
@@ -480,7 +498,7 @@
             mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
                                                IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
-    ASSERT_GE(mIncrementalService->setStorageParams(storageId, true), 0);
+    ASSERT_GE(mDataLoaderManager->setStorageParams(true), 0);
     ASSERT_NE(nullptr, mAppOpsManager->mStoredCallback.get());
     mAppOpsManager->mStoredCallback->opChanged(0, {});
 }
@@ -503,7 +521,7 @@
             mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
                                                IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
-    ASSERT_LT(mIncrementalService->setStorageParams(storageId, true), 0);
+    ASSERT_LT(mDataLoaderManager->setStorageParams(true), 0);
 }
 
 TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsFails) {
@@ -526,7 +544,7 @@
             mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
                                                IncrementalService::CreateOptions::CreateNew);
     ASSERT_GE(storageId, 0);
-    ASSERT_LT(mIncrementalService->setStorageParams(storageId, true), 0);
+    ASSERT_LT(mDataLoaderManager->setStorageParams(true), 0);
 }
 
 TEST_F(IncrementalServiceTest, testMakeDirectory) {
diff --git a/services/net/Android.bp b/services/net/Android.bp
index c54102f..3eba6c4 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -13,7 +13,7 @@
     ],
     static_libs: [
         "dnsresolver_aidl_interface-V2-java",
-        "netd_aidl_interface-unstable-java",
+        "netd_aidl_interface-V3-java",
         "netlink-client",
         "networkstack-client",
         "net-utils-services-common",
@@ -44,7 +44,7 @@
     ],
     static_libs: [
         "dnsresolver_aidl_interface-V2-java",
-        "netd_aidl_interface-unstable-java",
+        "netd_aidl_interface-V3-java",
         "netlink-client",
         "networkstack-client",
         "net-utils-services-common",
diff --git a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
index c225d3f..1aab672 100644
--- a/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
+++ b/services/systemcaptions/java/com/android/server/systemcaptions/RemoteSystemCaptionsManagerService.java
@@ -16,6 +16,8 @@
 
 package com.android.server.systemcaptions;
 
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
@@ -37,6 +39,8 @@
     private static final String SERVICE_INTERFACE =
             "android.service.systemcaptions.SystemCaptionsManagerService";
 
+    private static final int MSG_BIND = 1;
+
     private final Object mLock = new Object();
 
     private final Context mContext;
@@ -71,18 +75,26 @@
         if (mVerbose) {
             Slog.v(TAG, "initialize()");
         }
-        ensureBound();
+        scheduleBind();
     }
 
-    void destroy() {
+    /**
+     * Destroys this service.
+     */
+    public void destroy() {
+        mHandler.sendMessage(
+                obtainMessage(RemoteSystemCaptionsManagerService::handleDestroy, this));
+    }
+
+    void handleDestroy() {
         if (mVerbose) {
-            Slog.v(TAG, "destroy()");
+            Slog.v(TAG, "handleDestroy()");
         }
 
         synchronized (mLock) {
             if (mDestroyed) {
                 if (mVerbose) {
-                    Slog.v(TAG, "destroy(): Already destroyed");
+                    Slog.v(TAG, "handleDestroy(): Already destroyed");
                 }
                 return;
             }
@@ -97,14 +109,24 @@
         }
     }
 
-    private void ensureBound() {
+    private void scheduleBind() {
+        if (mHandler.hasMessages(MSG_BIND)) {
+            if (mVerbose) Slog.v(TAG, "scheduleBind(): already scheduled");
+            return;
+        }
+        mHandler.sendMessage(
+                obtainMessage(RemoteSystemCaptionsManagerService::handleEnsureBound, this)
+                .setWhat(MSG_BIND));
+    }
+
+    private void handleEnsureBound() {
         synchronized (mLock) {
             if (mService != null || mBinding) {
                 return;
             }
 
             if (mVerbose) {
-                Slog.v(TAG, "ensureBound(): binding");
+                Slog.v(TAG, "handleEnsureBound(): binding");
             }
             mBinding = true;
 
diff --git a/services/tests/servicestests/res/raw/comp_policies_primary.xml b/services/tests/servicestests/res/raw/comp_policies_primary.xml
index d30f479..395b8ab 100644
--- a/services/tests/servicestests/res/raw/comp_policies_primary.xml
+++ b/services/tests/servicestests/res/raw/comp_policies_primary.xml
@@ -3,6 +3,11 @@
     <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
         <policies flags="991"/>
         <password-history-length value="33" />
+        <require_auto_time value="true" />
         <user-restrictions no_bluetooth="true" />
+        <disable-screen-capture value="true" />
+        <disable-account-management>
+            <account-type value="com.google-primary" />
+        </disable-account-management>
     </admin>
 </policies>
diff --git a/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml b/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml
index c874dcc..c65d056 100644
--- a/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml
+++ b/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml
@@ -2,5 +2,8 @@
 <policies setup-complete="true" provisioning-state="3">
     <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
         <policies flags="991"/>
+        <disable-account-management>
+            <account-type value="com.google-profile" />
+        </disable-account-management>
     </admin>
 </policies>
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index de2addf..74e7f8c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -19,6 +19,7 @@
 
 import static com.android.server.devicepolicy.DpmTestUtils.writeInputStreamToFile;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
@@ -378,6 +379,15 @@
                     33, dpm.getParentProfileInstance(admin1).getPasswordHistoryLength(admin1));
             assertEquals("Password history policy was put into non-parent PO instance",
                     0, dpm.getPasswordHistoryLength(admin1));
+            assertTrue("Screen capture restriction wasn't migrated to PO parent instance",
+                    dpm.getParentProfileInstance(admin1).getScreenCaptureDisabled(admin1));
+
+            assertArrayEquals("Accounts with management disabled weren't migrated to PO parent",
+                    new String[] {"com.google-primary"},
+                    dpm.getParentProfileInstance(admin1).getAccountTypesWithManagementDisabled());
+            assertArrayEquals("Accounts with management disabled for profile were lost",
+                    new String[] {"com.google-profile"},
+                    dpm.getAccountTypesWithManagementDisabled());
 
             assertTrue("User restriction wasn't migrated to PO parent instance",
                     dpm.getParentProfileInstance(admin1).getUserRestrictions(admin1)
@@ -385,7 +395,15 @@
             assertFalse("User restriction was put into non-parent PO instance",
                     dpm.getUserRestrictions(admin1).containsKey(UserManager.DISALLOW_BLUETOOTH));
 
-            // TODO(b/143516163): verify more policies.
+            assertTrue("User restriction wasn't migrated to PO parent instance",
+                    dpms.getProfileOwnerAdminLocked(COPE_PROFILE_USER_ID)
+                            .getParentActiveAdmin()
+                            .getEffectiveRestrictions()
+                            .containsKey(UserManager.DISALLOW_CONFIG_DATE_TIME));
+            assertFalse("User restriction was put into non-parent PO instance",
+                    dpms.getProfileOwnerAdminLocked(COPE_PROFILE_USER_ID)
+                            .getEffectiveRestrictions()
+                            .containsKey(UserManager.DISALLOW_CONFIG_DATE_TIME));
         });
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index d780370..fe224ce 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2032,13 +2032,17 @@
                 eq(false));
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(UserManager.DISALLOW_CAMERA),
-                parentDpm.getUserRestrictions(admin1)
+                dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                        .getParentActiveAdmin()
+                        .getEffectiveRestrictions()
         );
 
         parentDpm.setCameraDisabled(admin1, false);
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(),
-                parentDpm.getUserRestrictions(admin1)
+                dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                        .getParentActiveAdmin()
+                        .getEffectiveRestrictions()
         );
         reset(getServices().userManagerInternal);
     }
@@ -2053,7 +2057,9 @@
         parentDpm.clearUserRestriction(admin1, restriction);
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(),
-                parentDpm.getUserRestrictions(admin1)
+                dpms.getProfileOwnerAdminLocked(DpmMockContext.CALLER_USER_HANDLE)
+                        .getParentActiveAdmin()
+                        .getEffectiveRestrictions()
         );
     }
 
@@ -2088,11 +2094,7 @@
     private void assertNoDeviceOwnerRestrictions() {
         DpmTestUtils.assertRestrictions(
                 DpmTestUtils.newRestrictions(),
-                getDeviceOwner().ensureUserRestrictions()
-        );
-        DpmTestUtils.assertRestrictions(
-                DpmTestUtils.newRestrictions(),
-                dpm.getUserRestrictions(admin1)
+                getDeviceOwner().getEffectiveRestrictions()
         );
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java b/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java
index 7c9a81d..a525814 100644
--- a/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java
@@ -43,7 +43,9 @@
 @RunWith(AndroidJUnit4.class)
 public class AppSaturationControllerTest {
 
-    private static final String TEST_PACKAGE_NAME = "com.android.test";
+    private static final String TEST_CALLER_PACKAGE_NAME = "com.android.test.caller";
+    private static final String TEST_CALLER_PACKAGE_NAME_TWO = "com.android.test.caller.two";
+    private static final String TEST_AFFECTED_PACKAGE_NAME = "com.android.test.affected";
 
     private int mUserId;
     private AppSaturationController mAppSaturationController;
@@ -70,8 +72,11 @@
     public void addColorTransformController_appliesExistingSaturation() {
         final WeakReference<ColorTransformController> ref = new WeakReference<>(
                 mColorTransformController);
-        mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 30);
-        mAppSaturationController.addColorTransformController(TEST_PACKAGE_NAME, mUserId, ref);
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        30);
+        mAppSaturationController
+                .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
         AppSaturationController.computeGrayscaleTransformMatrix(.3f, mMatrix);
         verify(mColorTransformController).applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
     }
@@ -80,14 +85,19 @@
     public void setSaturationLevel_resetToDefault() {
         final WeakReference<ColorTransformController> ref = new WeakReference<>(
                 mColorTransformController);
-        mAppSaturationController.addColorTransformController(TEST_PACKAGE_NAME, mUserId, ref);
+        mAppSaturationController
+                .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
         verify(mColorTransformController, never())
                 .applyAppSaturation(any(), eq(TRANSLATION_VECTOR));
-        mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 30);
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        30);
         AppSaturationController.computeGrayscaleTransformMatrix(.3f, mMatrix);
         verify(mColorTransformController, times(1))
                 .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
-        mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 100);
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        100);
         AppSaturationController.computeGrayscaleTransformMatrix(1.0f, mMatrix);
         verify(mColorTransformController, times(2))
                 .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
@@ -97,19 +107,76 @@
     public void setSaturationLevel_updateLevel() {
         final WeakReference<ColorTransformController> ref = new WeakReference<>(
                 mColorTransformController);
-        mAppSaturationController.addColorTransformController(TEST_PACKAGE_NAME, mUserId, ref);
+        mAppSaturationController
+                .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
         verify(mColorTransformController, never())
                 .applyAppSaturation(any(), eq(TRANSLATION_VECTOR));
-        mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 30);
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        30);
         AppSaturationController.computeGrayscaleTransformMatrix(.3f, mMatrix);
         verify(mColorTransformController).applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
-        mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 70);
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        70);
         AppSaturationController.computeGrayscaleTransformMatrix(.7f, mMatrix);
         verify(mColorTransformController, times(2))
                 .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
-        mAppSaturationController.setSaturationLevel(TEST_PACKAGE_NAME, mUserId, 100);
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        100);
         AppSaturationController.computeGrayscaleTransformMatrix(1.0f, mMatrix);
         verify(mColorTransformController, times(3))
                 .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
     }
+
+    @Test
+    public void setSaturationLevel_multipleCallers_appliesStrongest() {
+        final WeakReference<ColorTransformController> ref = new WeakReference<>(
+                mColorTransformController);
+        mAppSaturationController
+                .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
+        verify(mColorTransformController, never())
+                .applyAppSaturation(any(), eq(TRANSLATION_VECTOR));
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        30);
+        AppSaturationController.computeGrayscaleTransformMatrix(0.3f, mMatrix);
+        verify(mColorTransformController, times(1))
+                .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME_TWO, TEST_AFFECTED_PACKAGE_NAME,
+                        mUserId,
+                        70);
+        verify(mColorTransformController, times(2))
+                .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
+    }
+
+    @Test
+    public void setSaturationLevel_multipleCallers_removingOneDoesNotAffectTheOther() {
+        final WeakReference<ColorTransformController> ref = new WeakReference<>(
+                mColorTransformController);
+        mAppSaturationController
+                .addColorTransformController(TEST_AFFECTED_PACKAGE_NAME, mUserId, ref);
+        verify(mColorTransformController, never())
+                .applyAppSaturation(any(), eq(TRANSLATION_VECTOR));
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME, TEST_AFFECTED_PACKAGE_NAME, mUserId,
+                        70);
+        AppSaturationController.computeGrayscaleTransformMatrix(0.7f, mMatrix);
+        verify(mColorTransformController, times(1))
+                .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME_TWO, TEST_AFFECTED_PACKAGE_NAME,
+                        mUserId,
+                        30);
+        AppSaturationController.computeGrayscaleTransformMatrix(0.3f, mMatrix);
+        verify(mColorTransformController, times(2))
+                .applyAppSaturation(eq(mMatrix), eq(TRANSLATION_VECTOR));
+        mAppSaturationController
+                .setSaturationLevel(TEST_CALLER_PACKAGE_NAME_TWO, TEST_AFFECTED_PACKAGE_NAME,
+                        mUserId,
+                        100);
+        AppSaturationController.computeGrayscaleTransformMatrix(0.7f, mMatrix);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java
index ccbaee4..aa923e2 100644
--- a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java
@@ -63,6 +63,16 @@
                 fakeHwLight(105, LightsManager.LIGHT_TYPE_MICROPHONE, 2)
             };
         }
+
+        @Override
+        public int getInterfaceVersion() {
+            return this.VERSION;
+        }
+
+        @Override
+        public String getInterfaceHash() {
+            return this.HASH;
+        }
     };
 
     private static HwLight fakeHwLight(int id, int type, int ordinal) {
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index 820e61c..9eda718 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -377,8 +377,7 @@
                 return false;
             }
             final String key = createKey(overlayPackage.packageName, userId);
-            mIdmapFiles.add(key);
-            return true;
+            return mIdmapFiles.add(key);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java
new file mode 100644
index 0000000..cfeadc6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/stats/pull/SettingsStatsUtilTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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.stats.pull;
+
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static com.android.internal.util.FrameworkStatsLog.SETTING_SNAPSHOT;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:SettingsStatsUtilTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class SettingsStatsUtilTest {
+    private static final String[] KEYS = new String[]{
+            "screen_auto_brightness_adj",
+            "font_scale"
+    };
+    private static final String ENCODED = "ChpzY3JlZW5fYXV0b19icmlnaHRuZXNzX2FkagoKZm9udF9zY2FsZQ";
+    private static final String FLAG = "testflag";
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                FLAG,
+                "",
+                false /* makeDefault*/);
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    @Test
+    public void getList_emptyString_nullValue() {
+        assertNull(SettingsStatsUtil.getList(FLAG));
+    }
+
+    @Test
+    public void getList_notValidString_nullValue() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, FLAG, "abcd", false);
+
+        assertNull(SettingsStatsUtil.getList(FLAG));
+    }
+
+    @Test
+    public void getList_validString_correctValue() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS, FLAG, ENCODED, false);
+
+        assertArrayEquals(KEYS, SettingsStatsUtil.getList(FLAG).element);
+    }
+
+    @Test
+    public void logGlobalSettings_noWhitelist_correctSize() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__boolean_whitelist", "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__integer_whitelist", "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__float_whitelist", "", false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__string_whitelist", "", false);
+
+        assertEquals(0, SettingsStatsUtil.logGlobalSettings(mContext, SETTING_SNAPSHOT,
+                USER_SYSTEM).size());
+    }
+
+    @Test
+    public void logGlobalSettings_correctSize() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__boolean_whitelist", ENCODED, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__integer_whitelist", ENCODED, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__float_whitelist", ENCODED, false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_STATS,
+                "GlobalFeature__string_whitelist", ENCODED, false);
+
+        assertEquals(KEYS.length * 4,
+                SettingsStatsUtil.logGlobalSettings(mContext, SETTING_SNAPSHOT,
+                        USER_SYSTEM).size());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java b/services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java
new file mode 100644
index 0000000..72580a3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.textclassifier;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Sanity test for {@link IconsContentProvider}.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class IconsContentProviderTest {
+
+    @Test
+    public void testLoadResource() {
+        final Context context = ApplicationProvider.getApplicationContext();
+        // Testing with the android package name because this is the only package name
+        // that returns the same uri across multiple classloaders.
+        final String packageName = "android";
+        final int resId = android.R.drawable.btn_star;
+        final Uri uri = IconsUriHelper.getInstance().getContentUri(packageName, resId);
+
+        final Drawable expected = Icon.createWithResource(packageName, resId).loadDrawable(context);
+        // Ensure we are testing with a non-empty image.
+        assertThat(expected.getIntrinsicWidth()).isGreaterThan(0);
+        assertThat(expected.getIntrinsicHeight()).isGreaterThan(0);
+
+        final Drawable actual = Icon.createWithContentUri(uri).loadDrawable(context);
+        assertThat(actual).isNotNull();
+        assertThat(IconsContentProvider.getBitmapData(actual))
+                .isEqualTo(IconsContentProvider.getBitmapData(expected));
+    }
+
+    @Test
+    public void testLoadResource_badUri() {
+        final Uri badUri = new Uri.Builder()
+                .scheme("content")
+                .authority(IconsUriHelper.AUTHORITY)
+                .path("badPackageId")
+                .appendPath("1234")
+                .build();
+
+        final Context context = ApplicationProvider.getApplicationContext();
+        assertThat(Icon.createWithContentUri(badUri).loadDrawable(context)).isNull();
+    }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/textclassifier/IconsUriHelperTest.java b/services/tests/servicestests/src/com/android/server/textclassifier/IconsUriHelperTest.java
new file mode 100644
index 0000000..96f09d9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/textclassifier/IconsUriHelperTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.textclassifier;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.Uri;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.textclassifier.IconsUriHelper.ResourceInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link IconsUriHelper}.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class IconsUriHelperTest {
+
+    private IconsUriHelper mIconsUriHelper;
+
+    @Before
+    public void setUp() {
+        mIconsUriHelper = IconsUriHelper.newInstanceForTesting(null);
+    }
+
+    @Test
+    public void testGetContentUri() {
+        final IconsUriHelper iconsUriHelper = IconsUriHelper.newInstanceForTesting(() -> "pkgId");
+        final Uri expected = new Uri.Builder()
+                .scheme("content")
+                .authority(IconsUriHelper.AUTHORITY)
+                .path("pkgId")
+                .appendPath("1234")
+                .build();
+
+        final Uri actual = iconsUriHelper.getContentUri("com.package.name", 1234);
+        assertThat(actual).isEqualTo(expected);
+    }
+
+    @Test
+    public void testGetContentUri_multiplePackages() {
+        final Uri uri1 = mIconsUriHelper.getContentUri("com.package.name1", 1234);
+        final Uri uri2 = mIconsUriHelper.getContentUri("com.package.name2", 5678);
+
+        assertThat(uri1.getScheme()).isEqualTo("content");
+        assertThat(uri2.getScheme()).isEqualTo("content");
+
+        assertThat(uri1.getAuthority()).isEqualTo(IconsUriHelper.AUTHORITY);
+        assertThat(uri2.getAuthority()).isEqualTo(IconsUriHelper.AUTHORITY);
+
+        assertThat(uri1.getPathSegments().get(1)).isEqualTo("1234");
+        assertThat(uri2.getPathSegments().get(1)).isEqualTo("5678");
+    }
+
+    @Test
+    public void testGetContentUri_samePackageIdForSamePackageName() {
+        final String packageName = "com.package.name";
+        final Uri uri1 = mIconsUriHelper.getContentUri(packageName, 1234);
+        final Uri uri2 = mIconsUriHelper.getContentUri(packageName, 5678);
+
+        final String id1 = uri1.getPathSegments().get(0);
+        final String id2 = uri2.getPathSegments().get(0);
+
+        assertThat(id1).isEqualTo(id2);
+    }
+
+    @Test
+    public void testGetResourceInfo() {
+        mIconsUriHelper.getContentUri("com.package.name1", 123);
+        final Uri uri = mIconsUriHelper.getContentUri("com.package.name2", 456);
+        mIconsUriHelper.getContentUri("com.package.name3", 789);
+
+        final ResourceInfo res = mIconsUriHelper.getResourceInfo(uri);
+        assertThat(res.packageName).isEqualTo("com.package.name2");
+        assertThat(res.id).isEqualTo(456);
+    }
+
+    @Test
+    public void testGetResourceInfo_unrecognizedUri() {
+        final Uri uri = new Uri.Builder()
+                .scheme("content")
+                .authority(IconsUriHelper.AUTHORITY)
+                .path("unrecognized")
+                .appendPath("1234")
+                .build();
+        assertThat(mIconsUriHelper.getResourceInfo(uri)).isNull();
+    }
+
+    @Test
+    public void testGetResourceInfo_invalidScheme() {
+        final IconsUriHelper iconsUriHelper = IconsUriHelper.newInstanceForTesting(() -> "pkgId");
+        iconsUriHelper.getContentUri("com.package.name", 1234);
+
+        final Uri uri = new Uri.Builder()
+                .scheme("file")
+                .authority(IconsUriHelper.AUTHORITY)
+                .path("pkgId")
+                .appendPath("1234")
+                .build();
+        assertThat(iconsUriHelper.getResourceInfo(uri)).isNull();
+    }
+
+    @Test
+    public void testGetResourceInfo_invalidAuthority() {
+        final IconsUriHelper iconsUriHelper = IconsUriHelper.newInstanceForTesting(() -> "pkgId");
+        iconsUriHelper.getContentUri("com.package.name", 1234);
+
+        final Uri uri = new Uri.Builder()
+                .scheme("content")
+                .authority("invalid.authority")
+                .path("pkgId")
+                .appendPath("1234")
+                .build();
+        assertThat(iconsUriHelper.getResourceInfo(uri)).isNull();
+    }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 327cfc7..39062f0 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -818,6 +818,69 @@
         assertBucket(STANDBY_BUCKET_RESTRICTED);
     }
 
+    /**
+     * Test that an app is "timed out" into the RESTRICTED bucket if prediction tries to put it into
+     * a low bucket after the RESTRICTED timeout.
+     */
+    @Test
+    public void testRestrictedTimeoutOverridesRestoredLowBucketPrediction() {
+        reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
+        assertBucket(STANDBY_BUCKET_ACTIVE);
+
+        // Predict to RARE Not long enough to time out into RESTRICTED.
+        mInjector.mElapsedRealtime += RARE_THRESHOLD;
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+                REASON_MAIN_PREDICTED);
+        assertBucket(STANDBY_BUCKET_RARE);
+
+        // Add a short timeout event
+        mInjector.mElapsedRealtime += 1000;
+        reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
+        assertBucket(STANDBY_BUCKET_ACTIVE);
+        mInjector.mElapsedRealtime += 1000;
+        mController.checkIdleStates(USER_ID);
+        assertBucket(STANDBY_BUCKET_ACTIVE);
+
+        // Long enough that it could have timed out into RESTRICTED. Instead of reverting to
+        // predicted RARE, should go into RESTRICTED
+        mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
+        mController.checkIdleStates(USER_ID);
+        assertBucket(STANDBY_BUCKET_RESTRICTED);
+
+        // Ensure that prediction can still raise it out despite this override.
+        mInjector.mElapsedRealtime += 1;
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
+                REASON_MAIN_PREDICTED);
+        assertBucket(STANDBY_BUCKET_ACTIVE);
+    }
+
+    /**
+     * Test that an app is "timed out" into the RESTRICTED bucket if prediction tries to put it into
+     * a low bucket after the RESTRICTED timeout.
+     */
+    @Test
+    public void testRestrictedTimeoutOverridesPredictionLowBucket() {
+        reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
+
+        // Not long enough to time out into RESTRICTED.
+        mInjector.mElapsedRealtime += RARE_THRESHOLD;
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+                REASON_MAIN_PREDICTED);
+        assertBucket(STANDBY_BUCKET_RARE);
+
+        mInjector.mElapsedRealtime += 1;
+        reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
+
+        // Long enough that it could have timed out into RESTRICTED.
+        mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD * 4;
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
+                REASON_MAIN_PREDICTED);
+        assertBucket(STANDBY_BUCKET_ACTIVE);
+        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+                REASON_MAIN_PREDICTED);
+        assertBucket(STANDBY_BUCKET_RESTRICTED);
+    }
+
     @Test
     public void testPredictionRaiseFromRestrictedTimeout_highBucket() {
         reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index 2077ecb..96c69af 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -197,7 +197,8 @@
         final String PREFIX = "Launcher: ComponentInfo{";
         final String POSTFIX = "}";
         final List<String> result = runShortcutCommandForSuccess(
-                instrumentation, "get-default-launcher");
+                instrumentation, "get-default-launcher --user "
+                + instrumentation.getContext().getUserId());
         for (String s : result) {
             if (s.startsWith(PREFIX) && s.endsWith(POSTFIX)) {
                 return s.substring(PREFIX.length(), s.length() - POSTFIX.length());
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 12bdec6..0568be8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java
@@ -22,6 +22,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
+import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
 
 import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
 import static com.android.server.wm.DisplayArea.Type.ANY;
@@ -72,7 +73,11 @@
         WindowManagerService wms = mSystemServices.getWindowManagerService();
         DisplayArea.Root root = new SurfacelessDisplayAreaRoot(wms);
         DisplayArea<WindowContainer> ime = new DisplayArea<>(wms, ABOVE_TASKS, "Ime");
-        DisplayArea<ActivityStack> tasks = new DisplayArea<>(wms, ANY, "Tasks");
+        DisplayContent displayContent = mock(DisplayContent.class);
+        TaskDisplayArea taskDisplayArea = new TaskDisplayArea(displayContent, wms, "Tasks",
+                FEATURE_DEFAULT_TASK_CONTAINER);
+        List<TaskDisplayArea> taskDisplayAreaList = new ArrayList<>();
+        taskDisplayAreaList.add(taskDisplayArea);
 
         final Feature foo;
         final Feature bar;
@@ -86,7 +91,7 @@
                         .all()
                         .except(TYPE_STATUS_BAR)
                         .build())
-                .build(wms, mock(DisplayContent.class), root, ime, tasks);
+                .build(wms, displayContent, root, ime, taskDisplayAreaList);
 
         policy.attachDisplayAreas();
 
@@ -98,9 +103,9 @@
         assertThat(policy.findAreaForToken(tokenOfType(TYPE_STATUS_BAR)),
                 is(not(decendantOfOneOf(policy.getDisplayAreas(bar)))));
 
-        assertThat(tasks,
+        assertThat(taskDisplayArea,
                 is(decendantOfOneOf(policy.getDisplayAreas(foo))));
-        assertThat(tasks,
+        assertThat(taskDisplayArea,
                 is(decendantOfOneOf(policy.getDisplayAreas(bar))));
 
         assertThat(ime,
@@ -109,7 +114,8 @@
                 is(decendantOfOneOf(policy.getDisplayAreas(bar))));
 
         List<DisplayArea<?>> actualOrder = collectLeafAreas(root);
-        Map<DisplayArea<?>, Set<Integer>> zSets = calculateZSets(policy, root, ime, tasks);
+        Map<DisplayArea<?>, Set<Integer>> zSets = calculateZSets(policy, root, ime,
+                taskDisplayArea);
         actualOrder = actualOrder.stream().filter(zSets::containsKey).collect(toList());
 
         Map<DisplayArea<?>, Integer> expectedByMinLayer = mapValues(zSets,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
index 4e4627b..6834ee5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaProviderTest.java
@@ -77,8 +77,7 @@
 
         @Override
         public DisplayAreaPolicy instantiate(WindowManagerService wmService, DisplayContent content,
-                DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer,
-                TaskDisplayArea taskDisplayArea) {
+                DisplayArea.Root root, DisplayArea<? extends WindowContainer> imeContainer) {
             throw new RuntimeException("test stub");
         }
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
index 618e608..880c486 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -54,17 +54,6 @@
     }
 
     @Test
-    public void testDisplayArea_positionChanged_throwsIfIncompatibleSibling() {
-        WindowManagerService wms = mWmsRule.getWindowManagerService();
-        DisplayArea<WindowContainer> parent = new SurfacelessDisplayArea<>(wms, ANY, "Parent");
-        DisplayArea<WindowContainer> child1 = new DisplayArea<>(wms, ANY, "Child1");
-        DisplayArea<WindowContainer> child2 = new DisplayArea<>(wms, ANY, "Child2");
-
-        parent.addChild(child1, 0);
-        assertThrows(IllegalStateException.class, () -> parent.addChild(child2, 0));
-    }
-
-    @Test
     public void testType_typeOf() {
         WindowManagerService wms = mWmsRule.getWindowManagerService();
 
@@ -87,10 +76,10 @@
         checkSiblings(BELOW_TASKS, ABOVE_TASKS);
         checkSiblings(ANY, ABOVE_TASKS);
         checkSiblings(ABOVE_TASKS, ABOVE_TASKS);
+        checkSiblings(ANY, ANY);
 
         assertThrows(IllegalStateException.class, () -> checkSiblings(ABOVE_TASKS, BELOW_TASKS));
         assertThrows(IllegalStateException.class, () -> checkSiblings(ABOVE_TASKS, ANY));
-        assertThrows(IllegalStateException.class, () -> checkSiblings(ANY, ANY));
         assertThrows(IllegalStateException.class, () -> checkSiblings(ANY, BELOW_TASKS));
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 881561f..1f6ba7a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -238,9 +238,6 @@
         assertTrue(targetActivity.mLaunchTaskBehind);
 
         anotherHomeActivity.moveFocusableActivityToTop("launchAnotherHome");
-        // The current top activity is not the recents so the animation should be canceled.
-        verify(mService.mWindowManager, times(1)).cancelRecentsAnimation(
-                eq(REORDER_KEEP_IN_PLACE), any() /* reason */);
 
         // The test uses mocked RecentsAnimationController so we have to invoke the callback
         // manually to simulate the flow.
@@ -279,10 +276,6 @@
 
         fullscreenStack.moveToFront("Activity start");
 
-        // Ensure that the recents animation was canceled by cancelAnimationSynchronously().
-        verify(mService.mWindowManager, times(1)).cancelRecentsAnimation(
-                eq(REORDER_KEEP_IN_PLACE), any());
-
         // Assume recents animation already started, set a state that cancel recents animation
         // with screenshot.
         doReturn(true).when(mRecentsAnimationController).shouldDeferCancelUntilNextTransition();
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 6734694..e47792f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -243,6 +243,26 @@
     }
 
     @Test
+    public void testAspectRatioMatchParentBoundsAndImeAttachable() {
+        setUpApp(new TestDisplayContent.Builder(mService, 1000, 2000)
+                .setSystemDecorations(true).build());
+        prepareUnresizable(2f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
+        assertFitted();
+
+        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+        mActivity.mDisplayContent.mInputMethodTarget = addWindowToActivity(mActivity);
+        // Because the aspect ratio of display doesn't exceed the max aspect ratio of activity.
+        // The activity should still fill its parent container and IME can attach to the activity.
+        assertTrue(mActivity.matchParentBounds());
+        assertTrue(mActivity.mDisplayContent.isImeAttachedToApp());
+
+        final Rect letterboxInnerBounds = new Rect();
+        mActivity.getLetterboxInnerBounds(letterboxInnerBounds);
+        // The activity should not have letterbox.
+        assertTrue(letterboxInnerBounds.isEmpty());
+    }
+
+    @Test
     public void testMoveToDifferentOrientDisplay() {
         setUpDisplaySizeWithApp(1000, 2500);
         prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index 9625ffd..a708533 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -51,10 +51,10 @@
 import org.junit.runner.RunWith;
 
 /**
- * Tests for the {@link DisplayContent.TaskStackContainers} container in {@link DisplayContent}.
+ * Tests for the {@link TaskDisplayArea} container.
  *
  * Build/Install/Run:
- *  atest WmTests:TaskStackContainersTests
+ *  atest WmTests:TaskDisplayAreaTests
  */
 @SmallTest
 @Presubmit
@@ -154,8 +154,9 @@
                 ACTIVITY_TYPE_STANDARD, mDisplayContent);
         final Task newStack = createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN,
                 ACTIVITY_TYPE_STANDARD, mDisplayContent);
-        doReturn(newStack).when(mDisplayContent.mTaskContainers).createStack(anyInt(),
-                anyInt(), anyBoolean(), any(), any(), anyBoolean());
+        final TaskDisplayArea taskDisplayArea = candidateTask.getDisplayArea();
+        doReturn(newStack).when(taskDisplayArea).createStack(anyInt(), anyInt(), anyBoolean(),
+                any(), any(), anyBoolean());
 
         final int type = ACTIVITY_TYPE_STANDARD;
         assertGetOrCreateStack(WINDOWING_MODE_FULLSCREEN, type, candidateTask,
@@ -186,7 +187,7 @@
 
     private void assertGetOrCreateStack(int windowingMode, int activityType, Task candidateTask,
             boolean reuseCandidate) {
-        final TaskDisplayArea taskDisplayArea = (TaskDisplayArea) candidateTask.getParent();
+        final TaskDisplayArea taskDisplayArea = candidateTask.getDisplayArea();
         final ActivityStack stack = taskDisplayArea.getOrCreateStack(windowingMode, activityType,
                 false /* onTop */, null /* intent */, candidateTask /* candidateTask */);
         assertEquals(reuseCandidate, stack == candidateTask);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index 2ce9c2b..f275e37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -69,6 +69,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -117,6 +119,13 @@
         return createTaskStackOnDisplay(mDisplayContent);
     }
 
+    @Before
+    public void setUp() {
+        // We defer callbacks since we need to adjust task surface visibility, but for these tests,
+        // just run the callbacks synchronously
+        mWm.mAtmService.mTaskOrganizerController.setDeferTaskOrgCallbacksConsumer((r) -> r.run());
+    }
+
     @Test
     public void testAppearVanish() throws RemoteException {
         final ActivityStack stack = createStack();
@@ -192,6 +201,21 @@
     }
 
     @Test
+    public void testTaskNoDraw() throws RemoteException {
+        final ActivityStack stack = createStack();
+        final Task task = createTask(stack, false /* fakeDraw */);
+        final ITaskOrganizer organizer = registerMockOrganizer();
+
+        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        verify(organizer, never()).onTaskAppeared(any());
+        assertTrue(stack.isOrganized());
+
+        mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
+        verify(organizer, never()).onTaskVanished(any());
+        assertFalse(stack.isOrganized());
+    }
+
+    @Test
     public void testClearOrganizer() throws RemoteException {
         final ActivityStack stack = createStack();
         final Task task = createTask(stack);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index 18737c2..d2a2732 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -43,7 +43,9 @@
         // hard-code to FULLSCREEN for tests.
         setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         spyOn(this);
-        spyOn(mTaskContainers);
+        for (int i = getTaskDisplayAreaCount() - 1; i >= 0; --i) {
+            spyOn(getTaskDisplayAreaAt(i));
+        }
 
         final DisplayRotation displayRotation = getDisplayRotation();
         spyOn(displayRotation);
@@ -137,6 +139,7 @@
             spyOn(displayPolicy);
             if (mSystemDecorations) {
                 doReturn(true).when(newDisplay).supportsSystemDecorations();
+                doReturn(true).when(displayPolicy).hasNavigationBar();
             } else {
                 doReturn(false).when(displayPolicy).hasNavigationBar();
                 doReturn(false).when(displayPolicy).hasStatusBar();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 7a075a2..4a8e8da 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -120,8 +120,8 @@
             IWindow iWindow = mock(IWindow.class);
             doReturn(mock(IBinder.class)).when(iWindow).asBinder();
             window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, activity,
-                    "Starting window", 0 /* ownerId */, false /* internalWindows */, wm,
-                    mock(Session.class), iWindow, mPowerManagerWrapper);
+                    "Starting window", 0 /* ownerId */, 0 /* userId*/, false /* internalWindows */,
+                    wm, mock(Session.class), iWindow, mPowerManagerWrapper);
             activity.startingWindow = window;
         }
         if (mRunnableWhenAddingSplashScreen != null) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index da4bde5..79b9ae1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -16,13 +16,18 @@
 
 package com.android.server.wm;
 
+import static android.os.Process.INVALID_UID;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.content.pm.PackageManager;
 import android.os.IBinder;
@@ -85,4 +90,19 @@
         assertFalse(windowToken.mRoundedCornerOverlay);
         assertTrue(windowToken.mFromClientToken);
     }
+
+    @Test(expected = SecurityException.class)
+    public void testRemoveWindowToken_ownerUidNotMatch_throwException() {
+        IBinder token = mock(IBinder.class);
+        mWm.addWindowTokenWithOptions(token, TYPE_TOAST, mDisplayContent.getDisplayId(),
+                null /* options */, null /* options */);
+
+        spyOn(mWm);
+        when(mWm.checkCallingPermission(anyString(), anyString())).thenReturn(false);
+        WindowToken windowToken = mWm.mRoot.getWindowToken(token);
+        spyOn(windowToken);
+        when(windowToken.getOwnerUid()).thenReturn(INVALID_UID);
+
+        mWm.removeWindowToken(token, mDisplayContent.getDisplayId());
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index 084216a..fc95556 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -112,7 +112,7 @@
 
         TestWindowState(WindowManagerService service, Session session, IWindow window,
                 WindowManager.LayoutParams attrs, WindowToken token) {
-            super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0,
+            super(service, session, window, token, null, OP_NONE, 0, attrs, 0, 0, 0,
                     false /* ownerCanAddInternalSystemWindow */);
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 397f73c..e561c13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -41,6 +41,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.os.UserHandle;
 import android.util.Log;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -296,12 +297,13 @@
 
     WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
             int ownerId, boolean ownerCanAddInternalSystemWindow) {
-        return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow,
-                mWm, mMockSession, mIWindow, mSystemServicesTestRule.getPowerManagerWrapper());
+        return createWindow(parent, type, token, name, ownerId, UserHandle.getUserId(ownerId),
+                ownerCanAddInternalSystemWindow, mWm, mMockSession, mIWindow,
+                mSystemServicesTestRule.getPowerManagerWrapper());
     }
 
     static WindowState createWindow(WindowState parent, int type, WindowToken token,
-            String name, int ownerId, boolean ownerCanAddInternalSystemWindow,
+            String name, int ownerId, int userId, boolean ownerCanAddInternalSystemWindow,
             WindowManagerService service, Session session, IWindow iWindow,
             WindowState.PowerManagerWrapper powerManagerWrapper) {
         synchronized (service.mGlobalLock) {
@@ -309,8 +311,8 @@
             attrs.setTitle(name);
 
             final WindowState w = new WindowState(service, session, iWindow, token, parent,
-                    OP_NONE,
-                    0, attrs, VISIBLE, ownerId, ownerCanAddInternalSystemWindow,
+                    OP_NONE, 0, attrs, VISIBLE, ownerId, userId,
+                    ownerCanAddInternalSystemWindow,
                     powerManagerWrapper);
             // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
             // adding it to the token...
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index 7a347cb..535d53e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.os.Process.INVALID_UID;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -28,6 +29,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 
+import android.content.res.Configuration;
 import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
 
@@ -133,6 +135,30 @@
         assertEquals(0, token.getWindowsCount());
     }
 
+    @Test
+    public void testClearFixedRotationTransform() {
+        final WindowToken appToken = mAppWindow.mToken;
+        final WindowToken wallpaperToken = mWallpaperWindow.mToken;
+        final Configuration config = new Configuration(mDisplayContent.getConfiguration());
+        final int originalRotation = config.windowConfiguration.getRotation();
+        final int targetRotation = (originalRotation + 1) % 4;
+
+        config.windowConfiguration.setRotation(targetRotation);
+        appToken.applyFixedRotationTransform(mDisplayInfo, mDisplayContent.mDisplayFrames, config);
+        wallpaperToken.linkFixedRotationTransform(appToken);
+
+        // The window tokens should apply the rotation by the transformation.
+        assertEquals(targetRotation, appToken.getWindowConfiguration().getRotation());
+        assertEquals(targetRotation, wallpaperToken.getWindowConfiguration().getRotation());
+
+        // The display doesn't rotate, the transformation will be canceled.
+        mAppWindow.mToken.clearFixedRotationTransform(null /* applyDisplayRotation */);
+
+        // The window tokens should restore to the original rotation.
+        assertEquals(originalRotation, appToken.getWindowConfiguration().getRotation());
+        assertEquals(originalRotation, wallpaperToken.getWindowConfiguration().getRotation());
+    }
+
     /**
      * Test that {@link WindowToken} constructor parameters is set with expectation.
      */
@@ -152,7 +178,7 @@
 
         token = new WindowToken(mDisplayContent.mWmService, mock(IBinder.class), TYPE_TOAST,
                 true /* persistOnEmpty */, mDisplayContent, true /* ownerCanManageAppTokens */,
-                true /* roundedCornerOverlay */, true /* fromClientToken */);
+                INVALID_UID, true /* roundedCornerOverlay */, true /* fromClientToken */);
         assertTrue(token.mRoundedCornerOverlay);
         assertTrue(token.mFromClientToken);
     }
@@ -166,7 +192,7 @@
     public void testSurfaceCreatedForWindowToken() {
         final WindowToken fromClientToken = new WindowToken(mDisplayContent.mWmService,
                 mock(IBinder.class), TYPE_APPLICATION_OVERLAY, true /* persistOnEmpty */,
-                mDisplayContent, true /* ownerCanManageAppTokens */,
+                mDisplayContent, true /* ownerCanManageAppTokens */, INVALID_UID,
                 true /* roundedCornerOverlay */, true /* fromClientToken */);
         assertNull(fromClientToken.mSurfaceControl);
 
@@ -175,7 +201,7 @@
 
         final WindowToken nonClientToken = new WindowToken(mDisplayContent.mWmService,
                 mock(IBinder.class), TYPE_TOAST, true /* persistOnEmpty */, mDisplayContent,
-                true /* ownerCanManageAppTokens */, true /* roundedCornerOverlay */,
+                true /* ownerCanManageAppTokens */, INVALID_UID, true /* roundedCornerOverlay */,
                 false /* fromClientToken */);
         assertNotNull(nonClientToken.mSurfaceControl);
     }
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index d5851d8..0c25cfb 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -34,18 +34,21 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
+import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.IoThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.pm.BackgroundDexOptService;
 import com.android.server.wm.ActivityMetricsLaunchObserver;
 import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
 import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
 import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
+import java.util.ArrayList;
 import java.util.concurrent.TimeUnit;
 import java.util.HashMap;
 
@@ -286,6 +289,7 @@
 
     private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver();
     private final EventSequenceValidator mEventSequenceValidator = new EventSequenceValidator();
+    private final DexOptPackagesUpdated mDexOptPackagesUpdated = new DexOptPackagesUpdated();
     private boolean mRegisteredListeners = false;
 
     private void registerInProcessListenersLocked() {
@@ -308,9 +312,22 @@
         launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
         launchObserverRegistry.registerLaunchObserver(mEventSequenceValidator);
 
+        BackgroundDexOptService.addPackagesUpdatedListener(mDexOptPackagesUpdated);
+
+
         mRegisteredListeners = true;
     }
 
+    private class DexOptPackagesUpdated implements BackgroundDexOptService.PackagesUpdatedListener {
+        @Override
+        public void onPackagesUpdated(ArraySet<String> updatedPackages) {
+            String[] updated = updatedPackages.toArray(new String[0]);
+            for (String packageName : updated) {
+                Log.d(TAG, "onPackagesUpdated: " + packageName);
+            }
+        }
+    }
+
     private class AppLaunchObserver implements ActivityMetricsLaunchObserver {
         // We add a synthetic sequence ID here to make it easier to differentiate new
         // launch sequences on the native side.
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index f019a9d..4e14fd3 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -74,6 +74,7 @@
         public void onConnectionEvent(Conference c, String event, Bundle extras) {}
         public void onCallerDisplayNameChanged(
                 Conference c, String callerDisplayName, int presentation) {}
+        public void onCallDirectionChanged(Conference c, int callDirection) {}
         public void onRingbackRequested(Conference c, boolean ringback) {}
     }
 
@@ -103,6 +104,7 @@
     private int mAddressPresentation;
     private String mCallerDisplayName;
     private int mCallerDisplayNamePresentation;
+    private int mCallDirection;
     private boolean mRingbackRequested = false;
 
     private final Connection.Listener mConnectionDeathListener = new Connection.Listener() {
@@ -1024,6 +1026,25 @@
     }
 
     /**
+     * Sets the call direction of this {@link Conference}. By default, all {@link Conference}s have
+     * a direction of {@link android.telecom.Call.Details.CallDirection#DIRECTION_UNKNOWN}. The
+     * direction of a {@link Conference} is only applicable to the case where
+     * {@link #setConferenceState(boolean)} has been set to {@code false}, otherwise the direction
+     * will be ignored.
+     * @param callDirection The direction of the conference.
+     * @hide
+     */
+    @RequiresPermission(MODIFY_PHONE_STATE)
+    public final void setCallDirection(@Call.Details.CallDirection int callDirection) {
+        Log.d(this, "setDirection %d", callDirection);
+        mCallDirection = callDirection;
+        for (Listener l : mListeners) {
+            l.onCallDirectionChanged(this, callDirection);
+        }
+    }
+
+
+    /**
      * Sets the address of this {@link Conference}.  Used when {@link #setConferenceState(boolean)}
      * is called to mark a conference temporarily as NOT a conference.
      * <p>
@@ -1071,16 +1092,16 @@
      * This is applicable in two cases:
      * <ol>
      *     <li>When {@link #setConferenceState(boolean)} is used to mark a conference as
-     *     temporarily "not a conference"; we need to present the correct address in the in-call
-     *     UI.</li>
+     *     temporarily "not a conference"; we need to present the correct address presentation in
+     *     the in-call UI.</li>
      *     <li>When the conference is not hosted on the current device, we need to know the address
-     *     information for the purpose of showing the original address to the user, as well as for
-     *     logging to the call log.</li>
+     *     presentation information for the purpose of showing the original address to the user, as
+     *     well as for logging to the call log.</li>
      * </ol>
-     * @return The address of the conference, or {@code null} if not applicable.
+     * @return The address presentation of the conference.
      * @hide
      */
-    public final int getAddressPresentation() {
+    public final @TelecomManager.Presentation int getAddressPresentation() {
         return mAddressPresentation;
     }
 
@@ -1102,6 +1123,15 @@
     }
 
     /**
+     * @return The call direction of this conference. Only applicable when
+     * {@link #setConferenceState(boolean)} is set to false.
+     * @hide
+     */
+    public final @Call.Details.CallDirection int getCallDirection() {
+        return mCallDirection;
+    }
+
+    /**
      * Sets the caller display name (CNAP) of this {@link Conference}.  Used when
      * {@link #setConferenceState(boolean)} is called to mark a conference temporarily as NOT a
      * conference.
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index ffd25c0..73296986 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1554,6 +1554,14 @@
         }
 
         @Override
+        public void onCallDirectionChanged(Conference c, int direction) {
+            String id = mIdByConference.get(c);
+            if (id != null) {
+                mAdapter.setCallDirection(id, direction);
+            }
+        }
+
+        @Override
         public void onAddressChanged(Conference c, Uri newAddress, int presentation) {
             String id = mIdByConference.get(c);
             if (id != null) {
@@ -1857,25 +1865,23 @@
         mConferenceById.put(callId, conference);
         mIdByConference.put(conference, callId);
         conference.addListener(mConferenceListener);
-        ParcelableConference parcelableConference = new ParcelableConference(
-                request.getAccountHandle(),
-                conference.getState(),
-                conference.getConnectionCapabilities(),
-                conference.getConnectionProperties(),
-                Collections.<String>emptyList(), //connectionIds
-                conference.getVideoProvider() == null ?
-                        null : conference.getVideoProvider().getInterface(),
-                conference.getVideoState(),
-                conference.getConnectTimeMillis(),
-                conference.getConnectionStartElapsedRealtimeMillis(),
-                conference.getStatusHints(),
-                conference.getExtras(),
-                conference.getAddress(),
-                conference.getAddressPresentation(),
-                conference.getCallerDisplayName(),
-                conference.getCallerDisplayNamePresentation(),
-                conference.getDisconnectCause(),
-                conference.isRingbackRequested());
+        ParcelableConference parcelableConference = new ParcelableConference.Builder(
+                request.getAccountHandle(), conference.getState())
+                .setConnectionCapabilities(conference.getConnectionCapabilities())
+                .setConnectionProperties(conference.getConnectionProperties())
+                .setVideoAttributes(conference.getVideoProvider() == null
+                                ? null : conference.getVideoProvider().getInterface(),
+                        conference.getVideoState())
+                .setConnectTimeMillis(conference.getConnectTimeMillis(),
+                        conference.getConnectionStartElapsedRealtimeMillis())
+                .setStatusHints(conference.getStatusHints())
+                .setExtras(conference.getExtras())
+                .setAddress(conference.getAddress(), conference.getAddressPresentation())
+                .setCallerDisplayName(conference.getCallerDisplayName(),
+                        conference.getCallerDisplayNamePresentation())
+                .setDisconnectCause(conference.getDisconnectCause())
+                .setRingbackRequested(conference.isRingbackRequested())
+                .build();
         if (conference.getState() != Connection.STATE_DISCONNECTED) {
             conference.setTelecomCallId(callId);
             mAdapter.setVideoProvider(callId, conference.getVideoProvider());
@@ -2476,23 +2482,25 @@
                 }
             }
             conference.setTelecomCallId(id);
-            ParcelableConference parcelableConference = new ParcelableConference(
-                    conference.getPhoneAccountHandle(),
-                    conference.getState(),
-                    conference.getConnectionCapabilities(),
-                    conference.getConnectionProperties(),
-                    connectionIds,
-                    conference.getVideoProvider() == null ?
-                            null : conference.getVideoProvider().getInterface(),
-                    conference.getVideoState(),
-                    conference.getConnectTimeMillis(),
-                    conference.getConnectionStartElapsedRealtimeMillis(),
-                    conference.getStatusHints(),
-                    conference.getExtras(),
-                    conference.getAddress(),
-                    conference.getAddressPresentation(),
-                    conference.getCallerDisplayName(),
-                    conference.getCallerDisplayNamePresentation());
+            ParcelableConference parcelableConference = new ParcelableConference.Builder(
+                    conference.getPhoneAccountHandle(), conference.getState())
+                    .setConnectionCapabilities(conference.getConnectionCapabilities())
+                    .setConnectionProperties(conference.getConnectionProperties())
+                    .setConnectionIds(connectionIds)
+                    .setVideoAttributes(conference.getVideoProvider() == null
+                                    ? null : conference.getVideoProvider().getInterface(),
+                            conference.getVideoState())
+                    .setConnectTimeMillis(conference.getConnectTimeMillis(),
+                            conference.getConnectionStartElapsedRealtimeMillis())
+                    .setStatusHints(conference.getStatusHints())
+                    .setExtras(conference.getExtras())
+                    .setAddress(conference.getAddress(), conference.getAddressPresentation())
+                    .setCallerDisplayName(conference.getCallerDisplayName(),
+                            conference.getCallerDisplayNamePresentation())
+                    .setDisconnectCause(conference.getDisconnectCause())
+                    .setRingbackRequested(conference.isRingbackRequested())
+                    .setCallDirection(conference.getCallDirection())
+                    .build();
 
             mAdapter.addConferenceCall(id, parcelableConference);
             mAdapter.setVideoProvider(id, conference.getVideoProvider());
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index 8f27323..f8a6cf0 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -693,4 +693,20 @@
             }
         }
     }
+
+    /**
+     * Sets the direction of a call. Setting a new direction of an existing call is usually only
+     * applicable during single caller emulation during conferencing, see
+     * {@link Conference#setConferenceState(boolean)} for more information.
+     * @param callId The identifier of the call.
+     * @param direction The new direction of the call.
+     */
+    void setCallDirection(String callId, @Call.Details.CallDirection int direction) {
+        for (IConnectionServiceAdapter a : mAdapters) {
+            try {
+                a.setCallDirection(callId, direction, Log.getExternalSession());
+            } catch (RemoteException e) {
+            }
+        }
+    }
 }
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index 79ad51b..6c1ea32 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -76,6 +76,7 @@
     private static final int MSG_CONNECTION_SERVICE_FOCUS_RELEASED = 35;
     private static final int MSG_SET_CONFERENCE_STATE = 36;
     private static final int MSG_HANDLE_CREATE_CONFERENCE_COMPLETE = 37;
+    private static final int MSG_SET_CALL_DIRECTION = 38;
 
     private final IConnectionServiceAdapter mDelegate;
 
@@ -353,7 +354,7 @@
                 case MSG_CONNECTION_SERVICE_FOCUS_RELEASED:
                     mDelegate.onConnectionServiceFocusReleased(null /*Session.Info*/);
                     break;
-                case MSG_SET_CONFERENCE_STATE:
+                case MSG_SET_CONFERENCE_STATE: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
                         mDelegate.setConferenceState((String) args.arg1, (Boolean) args.arg2,
@@ -361,6 +362,17 @@
                     } finally {
                         args.recycle();
                     }
+                    break;
+                }
+                case MSG_SET_CALL_DIRECTION: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        mDelegate.setCallDirection((String) args.arg1, args.argi1,
+                                (Session.Info) args.arg2);
+                    } finally {
+                        args.recycle();
+                    }
+                }
             }
         }
     };
@@ -670,6 +682,16 @@
             args.arg3 = sessionInfo;
             mHandler.obtainMessage(MSG_SET_CONFERENCE_STATE, args).sendToTarget();
         }
+
+        @Override
+        public void setCallDirection(String callId, int direction,
+                Session.Info sessionInfo) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = callId;
+            args.argi1 = direction;
+            args.arg2 = sessionInfo;
+            mHandler.obtainMessage(MSG_SET_CALL_DIRECTION, args).sendToTarget();
+        }
     };
 
     public ConnectionServiceAdapterServant(IConnectionServiceAdapter delegate) {
diff --git a/telecomm/java/android/telecom/ParcelableConference.java b/telecomm/java/android/telecom/ParcelableConference.java
index 90b69a3..1f8aafb 100644
--- a/telecomm/java/android/telecom/ParcelableConference.java
+++ b/telecomm/java/android/telecom/ParcelableConference.java
@@ -22,6 +22,7 @@
 import android.os.Parcelable;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import com.android.internal.telecom.IVideoProvider;
@@ -32,25 +33,130 @@
  */
 public final class ParcelableConference implements Parcelable {
 
-    private PhoneAccountHandle mPhoneAccount;
-    private int mState;
-    private int mConnectionCapabilities;
-    private int mConnectionProperties;
-    private List<String> mConnectionIds;
-    private long mConnectTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED;
+    public static final class Builder {
+        private final PhoneAccountHandle mPhoneAccount;
+        private final int mState;
+        private int mConnectionCapabilities;
+        private int mConnectionProperties;
+        private List<String> mConnectionIds = Collections.emptyList();
+        private long mConnectTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED;
+        private IVideoProvider mVideoProvider;
+        private int mVideoState = VideoProfile.STATE_AUDIO_ONLY;
+        private StatusHints mStatusHints;
+        private Bundle mExtras;
+        private long mConnectElapsedTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED;
+        private Uri mAddress;
+        private int mAddressPresentation = TelecomManager.PRESENTATION_UNKNOWN;
+        private String mCallerDisplayName;
+        private int mCallerDisplayNamePresentation = TelecomManager.PRESENTATION_UNKNOWN;;
+        private DisconnectCause mDisconnectCause;
+        private boolean mRingbackRequested;
+        private int mCallDirection = Call.Details.DIRECTION_UNKNOWN;
+
+        public Builder(
+                PhoneAccountHandle phoneAccount,
+                int state) {
+            mPhoneAccount = phoneAccount;
+            mState = state;
+        }
+
+        public Builder setDisconnectCause(DisconnectCause cause) {
+            mDisconnectCause = cause;
+            return this;
+        }
+
+        public Builder setRingbackRequested(boolean requested) {
+            mRingbackRequested = requested;
+            return this;
+        }
+
+        public Builder setCallerDisplayName(String callerDisplayName,
+                @TelecomManager.Presentation int callerDisplayNamePresentation) {
+            mCallerDisplayName = callerDisplayName;
+            mCallerDisplayNamePresentation = callerDisplayNamePresentation;
+            return this;
+        }
+
+        public Builder setAddress(Uri address,
+                @TelecomManager.Presentation int addressPresentation) {
+            mAddress = address;
+            mAddressPresentation = addressPresentation;
+            return this;
+        }
+
+        public Builder setExtras(Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        public Builder setStatusHints(StatusHints hints) {
+            mStatusHints = hints;
+            return this;
+        }
+
+        public Builder setConnectTimeMillis(long connectTimeMillis, long connectElapsedTimeMillis) {
+            mConnectTimeMillis = connectTimeMillis;
+            mConnectElapsedTimeMillis = connectElapsedTimeMillis;
+            return this;
+        }
+
+        public Builder setVideoAttributes(IVideoProvider provider,
+                @VideoProfile.VideoState int videoState) {
+            mVideoProvider = provider;
+            mVideoState = videoState;
+            return this;
+        }
+
+        public Builder setConnectionIds(List<String> connectionIds) {
+            mConnectionIds = connectionIds;
+            return this;
+        }
+
+        public Builder setConnectionProperties(int properties) {
+            mConnectionProperties = properties;
+            return this;
+        }
+
+        public Builder setConnectionCapabilities(int capabilities) {
+            mConnectionCapabilities = capabilities;
+            return this;
+        }
+
+        public Builder setCallDirection(int callDirection) {
+            mCallDirection = callDirection;
+            return this;
+        }
+
+        public ParcelableConference build() {
+            return new ParcelableConference(mPhoneAccount, mState, mConnectionCapabilities,
+                    mConnectionProperties, mConnectionIds, mVideoProvider, mVideoState,
+                    mConnectTimeMillis, mConnectElapsedTimeMillis, mStatusHints, mExtras, mAddress,
+                    mAddressPresentation, mCallerDisplayName, mCallerDisplayNamePresentation,
+                    mDisconnectCause, mRingbackRequested, mCallDirection);
+        }
+    }
+
+
+    private final PhoneAccountHandle mPhoneAccount;
+    private final int mState;
+    private final int mConnectionCapabilities;
+    private final int mConnectionProperties;
+    private final List<String> mConnectionIds;
+    private final long mConnectTimeMillis;
     private final IVideoProvider mVideoProvider;
     private final int mVideoState;
-    private StatusHints mStatusHints;
-    private Bundle mExtras;
-    private long mConnectElapsedTimeMillis = Conference.CONNECT_TIME_NOT_SPECIFIED;
+    private final StatusHints mStatusHints;
+    private final Bundle mExtras;
+    private final long mConnectElapsedTimeMillis;
     private final Uri mAddress;
     private final int mAddressPresentation;
     private final String mCallerDisplayName;
     private final int mCallerDisplayNamePresentation;
-    private DisconnectCause mDisconnectCause;
-    private boolean mRingbackRequested;
+    private final DisconnectCause mDisconnectCause;
+    private final boolean mRingbackRequested;
+    private final int mCallDirection;
 
-    public ParcelableConference(
+    private ParcelableConference(
             PhoneAccountHandle phoneAccount,
             int state,
             int connectionCapabilities,
@@ -67,31 +173,8 @@
             String callerDisplayName,
             int callerDisplayNamePresentation,
             DisconnectCause disconnectCause,
-            boolean ringbackRequested) {
-        this(phoneAccount, state, connectionCapabilities, connectionProperties, connectionIds,
-                videoProvider, videoState, connectTimeMillis, connectElapsedTimeMillis,
-                statusHints, extras, address, addressPresentation, callerDisplayName,
-                callerDisplayNamePresentation);
-        mDisconnectCause = disconnectCause;
-        mRingbackRequested = ringbackRequested;
-    }
-
-    public ParcelableConference(
-            PhoneAccountHandle phoneAccount,
-            int state,
-            int connectionCapabilities,
-            int connectionProperties,
-            List<String> connectionIds,
-            IVideoProvider videoProvider,
-            int videoState,
-            long connectTimeMillis,
-            long connectElapsedTimeMillis,
-            StatusHints statusHints,
-            Bundle extras,
-            Uri address,
-            int addressPresentation,
-            String callerDisplayName,
-            int callerDisplayNamePresentation) {
+            boolean ringbackRequested,
+            int callDirection) {
         mPhoneAccount = phoneAccount;
         mState = state;
         mConnectionCapabilities = connectionCapabilities;
@@ -107,8 +190,9 @@
         mAddressPresentation = addressPresentation;
         mCallerDisplayName = callerDisplayName;
         mCallerDisplayNamePresentation = callerDisplayNamePresentation;
-        mDisconnectCause = null;
-        mRingbackRequested = false;
+        mDisconnectCause = disconnectCause;
+        mRingbackRequested = ringbackRequested;
+        mCallDirection = callDirection;
     }
 
     @Override
@@ -134,6 +218,8 @@
                 .append(mRingbackRequested)
                 .append(", disconnectCause: ")
                 .append(mDisconnectCause)
+                .append(", callDirection: ")
+                .append(mCallDirection)
                 .toString();
     }
 
@@ -192,10 +278,15 @@
     public boolean isRingbackRequested() {
         return mRingbackRequested;
     }
+
     public int getHandlePresentation() {
         return mAddressPresentation;
     }
 
+    public int getCallDirection() {
+        return mCallDirection;
+    }
+
     public static final @android.annotation.NonNull Parcelable.Creator<ParcelableConference> CREATOR =
             new Parcelable.Creator<ParcelableConference> () {
         @Override
@@ -220,12 +311,13 @@
             int callerDisplayNamePresentation = source.readInt();
             DisconnectCause disconnectCause = source.readParcelable(classLoader);
             boolean isRingbackRequested = source.readInt() == 1;
+            int callDirection = source.readInt();
 
             return new ParcelableConference(phoneAccount, state, capabilities, properties,
                     connectionIds, videoCallProvider, videoState, connectTimeMillis,
                     connectElapsedTimeMillis, statusHints, extras, address, addressPresentation,
                     callerDisplayName, callerDisplayNamePresentation, disconnectCause,
-                    isRingbackRequested);
+                    isRingbackRequested, callDirection);
         }
 
         @Override
@@ -261,5 +353,6 @@
         destination.writeInt(mCallerDisplayNamePresentation);
         destination.writeParcelable(mDisconnectCause, 0);
         destination.writeInt(mRingbackRequested ? 1 : 0);
+        destination.writeInt(mCallDirection);
     }
 }
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 76640e0..cad5b70 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -485,6 +485,11 @@
                 Session.Info sessionInfo) {
             // Do nothing
         }
+
+        @Override
+        public void setCallDirection(String callId, int direction, Session.Info sessionInfo) {
+            // Do nothing
+        }
     };
 
     private final ConnectionServiceAdapterServant mServant =
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index c993cfa..b974c56 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1534,12 +1534,22 @@
     /**
      * Return the line 1 phone number for given phone account.
      *
-     * Requires permission: {@link android.Manifest.permission#READ_PHONE_STATE}
+     * <p>Requires Permission:
+     *     {@link android.Manifest.permission#READ_SMS READ_SMS},
+     *     {@link android.Manifest.permission#READ_PHONE_NUMBERS READ_PHONE_NUMBERS},
+     *     or that the caller is the default SMS app for any API level.
+     *     {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+     *     for apps targeting SDK API level 29 and below.
      *
      * @param accountHandle The handle for the account retrieve a number for.
      * @return A string representation of the line 1 phone number.
      */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges or default SMS app
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.READ_PHONE_STATE,
+            android.Manifest.permission.READ_SMS,
+            android.Manifest.permission.READ_PHONE_NUMBERS
+            }, conditional = true)
     public String getLine1Number(PhoneAccountHandle accountHandle) {
         try {
             if (isServiceConnected()) {
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index 4f63e08..3fd7f949 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -132,4 +132,6 @@
     void resetConnectionTime(String callIdi, in Session.Info sessionInfo);
 
     void setConferenceState(String callId, boolean isConference, in Session.Info sessionInfo);
+
+    void setCallDirection(String callId, int direction, in Session.Info sessionInfo);
 }
diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
index f3e9de0..3048ad7 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -311,7 +311,7 @@
         }
         // If the user or profile is current, permission is granted.
         // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
-        return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, uid, pid);
+        return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, pid, uid);
     }
 
     private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 327e8b3..56f3c3e 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1147,6 +1147,21 @@
             "support_ims_conference_event_package_bool";
 
     /**
+     * Determines whether processing of conference event package data received on a device other
+     * than the conference host is supported.
+     * <p>
+     * When a device A merges calls B and C into a conference it is considered the conference host
+     * and B and C are considered the conference peers.
+     * <p>
+     * When {@code true}, the conference peer will display the conference state if it receives
+     * conference event package data from the network.  When {@code false}, the conference peer will
+     * ignore conference event package data received from the network.
+     * @hide
+     */
+    public static final String KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_ON_PEER_BOOL =
+            "support_ims_conference_event_package_on_peer_bool";
+
+    /**
      * Determines whether High Definition audio property is displayed in the dialer UI.
      * If {@code false}, remove the HD audio property from the connection so that HD audio related
      * UI is not displayed. If {@code true}, keep HD audio property as it is configured.
@@ -1163,6 +1178,25 @@
             "support_ims_conference_call_bool";
 
     /**
+     * Determines whether the device will locally disconnect an IMS conference when the participant
+     * count drops to zero.  When {@code true}, it is assumed the carrier does NOT disconnect a
+     * conference when the participant count drops to zero and that the device must do this by
+     * disconnecting the conference locally.  When {@code false}, it is assumed that the carrier
+     * is responsible for disconnecting the conference when there are no longer any participants
+     * present.
+     * <p>
+     * Note: both {@link #KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL} and
+     * {@link #KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL} must be true for this configuration to
+     * have any effect.
+     * <p>
+     * Defaults to {@code false}, meaning the carrier network is responsible for disconnecting an
+     * empty IMS conference.
+     * @hide
+     */
+    public static final String KEY_LOCAL_DISCONNECT_EMPTY_IMS_CONFERENCE_BOOL =
+            "local_disconnect_empty_ims_conference_bool";
+
+    /**
      * Determines whether video conference calls are supported by a carrier.  When {@code true},
      * video calls can be merged into conference calls, {@code false} otherwiwse.
      * <p>
@@ -3778,8 +3812,10 @@
         sDefaults.putBoolean(KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
+        sDefaults.putBoolean(KEY_LOCAL_DISCONNECT_EMPTY_IMS_CONFERENCE_BOOL, false);
         sDefaults.putBoolean(KEY_SUPPORT_MANAGE_IMS_CONFERENCE_CALL_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL, true);
+        sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_ON_PEER_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false);
         sDefaults.putBoolean(KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL, false);
         sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5);
diff --git a/telephony/java/android/telephony/CbGeoUtils.java b/telephony/java/android/telephony/CbGeoUtils.java
index c0ae99e..806bac0 100644
--- a/telephony/java/android/telephony/CbGeoUtils.java
+++ b/telephony/java/android/telephony/CbGeoUtils.java
@@ -128,6 +128,23 @@
         public String toString() {
             return "(" + lat + "," + lng + ")";
         }
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+
+            if (!(o instanceof LatLng)) {
+                return false;
+            }
+
+            LatLng l = (LatLng) o;
+            return lat == l.lat && lng == l.lng;
+        }
     }
 
     /**
@@ -280,6 +297,32 @@
             }
             return str;
         }
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+
+            if (!(o instanceof Polygon)) {
+                return false;
+            }
+
+            Polygon p = (Polygon) o;
+            if (mVertices.size() != p.mVertices.size()) {
+                return false;
+            }
+            for (int i = 0; i < mVertices.size(); i++) {
+                if (!mVertices.get(i).equals(p.mVertices.get(i))) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
     }
 
     /**
@@ -335,6 +378,24 @@
 
             return str;
         }
+
+        /**
+         * @hide
+         */
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+
+            if (!(o instanceof Circle)) {
+                return false;
+            }
+
+            Circle c = (Circle) o;
+            return mCenter.equals(c.mCenter)
+                    && Double.compare(mRadiusMeter, c.mRadiusMeter) == 0;
+        }
     }
 
     /**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index d6cdaa6..f623649 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -147,6 +147,10 @@
  * information unless it has the appropriate permissions declared in
  * its manifest file. Where permissions apply, they are noted in the
  * the methods through which you access the protected information.
+ *
+ * <p>TelephonyManager is intended for use on devices that implement
+ * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices
+ * that do not implement this feature, the behavior is not reliable.
  */
 @SystemService(Context.TELEPHONY_SERVICE)
 public class TelephonyManager {
@@ -5842,6 +5846,10 @@
      * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
      * for each active subscription.
      *
+     * <p>This method returns valid data for devices with
+     * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices
+     * that do not implement this feature, the behavior is not reliable.
+     *
      * @param executor the executor on which callback will be invoked.
      * @param callback a callback to receive CellInfo.
      */
@@ -5888,6 +5896,10 @@
      * {@link android.telephony.PhoneStateListener#onCellInfoChanged onCellInfoChanged()}
      * for each active subscription.
      *
+     * <p>This method returns valid data for devices with
+     * {@link android.content.pm.PackageManager#FEATURE_TELEPHONY FEATURE_TELEPHONY}. On devices
+     * that do not implement this feature, the behavior is not reliable.
+     *
      * @param workSource the requestor to whom the power consumption for this should be attributed.
      * @param executor the executor on which callback will be invoked.
      * @param callback a callback to receive CellInfo.
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 2b1d9e5..18e2592 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -113,6 +113,7 @@
     public static final int EVENT_5G_TIMER_HYSTERESIS = BASE + 53;
     public static final int EVENT_5G_TIMER_WATCHDOG = BASE + 54;
     public static final int EVENT_CARRIER_CONFIG_CHANGED = BASE + 55;
+    public static final int EVENT_SIM_STATE_UPDATED = BASE + 56;
 
     /***** Constants *****/
 
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index 13bf179..2d2f4db 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -107,7 +107,9 @@
     private static final int PROFILE_SAVE_SLEEP_TIMEOUT = 1000; // Allow 1s for the profile to save
     private static final int IORAP_TRACE_DURATION_TIMEOUT = 7000; // Allow 7s for trace to complete.
     private static final int IORAP_TRIAL_LAUNCH_ITERATIONS = 3;  // min 3 launches to merge traces.
-    private static final int IORAP_COMPILE_CMD_TIMEOUT = 600;  // in seconds: 10 minutes
+    private static final int IORAP_COMPILE_CMD_TIMEOUT = 60;  // in seconds: 1 minutes
+    private static final int IORAP_COMPILE_MIN_TRACES = 1;  // configure iorapd to need 1 trace.
+    private static final int IORAP_COMPILE_RETRIES = 3;  // retry compiler 3 times if it fails.
     private static final String LAUNCH_SUB_DIRECTORY = "launch_logs";
     private static final String LAUNCH_FILE = "applaunch.txt";
     private static final String TRACE_SUB_DIRECTORY = "atrace_logs";
@@ -132,9 +134,9 @@
     private static final String LAUNCH_ORDER_CYCLIC = "cyclic";
     private static final String LAUNCH_ORDER_SEQUENTIAL = "sequential";
     private static final String COMPILE_CMD = "cmd package compile -f -m %s %s";
-    private static final String IORAP_COMPILE_CMD = "cmd jobscheduler run -f android 283673059";
+    private static final String IORAP_COMPILE_CMD = "dumpsys iorapd --compile-package %s";
     private static final String IORAP_MAINTENANCE_CMD =
-            "iorap.cmd.maintenance --purge-package %s /data/misc/iorapd/sqlite.db";
+            "dumpsys iorapd --purge-package %s";
     private static final String IORAP_DUMPSYS_CMD = "dumpsys iorapd";
     private static final String SPEED_PROFILE_FILTER = "speed-profile";
     private static final String VERIFY_FILTER = "verify";
@@ -350,9 +352,9 @@
                     sleep(IORAP_TRACE_DURATION_TIMEOUT);
 
                     if (launch.getLaunchReason().equals(IORAP_TRIAL_LAUNCH_LAST)) {
-                        // run the iorap job scheduler and wait for iorap to compile fully.
-                        assertTrue(String.format("Not able to iorap-compile the app : %s", appPkgName),
-                                compileAppForIorap(appPkgName));
+                        // run the iorap compiler and wait for iorap to compile fully.
+                        // this throws an exception if it fails.
+                        compileAppForIorapWithRetries(appPkgName, IORAP_COMPILE_RETRIES);
                     }
                 }
 
@@ -506,6 +508,22 @@
     }
 
     /**
+     * Compile the app package using compilerFilter,
+     * retrying if the compilation command fails in between.
+     */
+    private void compileAppForIorapWithRetries(String appPkgName, int retries) throws IOException {
+        for (int i = 0; i < retries; ++i) {
+            if (compileAppForIorap(appPkgName)) {
+                return;
+            }
+            sleep(1000);
+        }
+
+        throw new IllegalStateException("compileAppForIorapWithRetries: timed out after "
+                + retries + " retries");
+    }
+
+    /**
      * Compile the app package using compilerFilter and return true or false
      * based on status of the compilation command.
      */
@@ -513,7 +531,7 @@
         String logcatTimestamp = getTimeNowForLogcat();
 
         getInstrumentation().getUiAutomation().
-                executeShellCommand(IORAP_COMPILE_CMD);
+                executeShellCommand(String.format(IORAP_COMPILE_CMD, appPkgName));
 
         int i = 0;
         for (i = 0; i < IORAP_COMPILE_CMD_TIMEOUT; ++i) {
@@ -525,7 +543,8 @@
             } else if (status == IorapCompilationStatus.INSUFFICIENT_TRACES) {
                 Log.e(TAG, "compileAppForIorap: failed due to insufficient traces");
                 logDumpsysIorapd(appPkgName);
-                return false;
+                throw new IllegalStateException(
+                        "compileAppForIorap: failed due to insufficient traces");
             } // else INCOMPLETE. keep asking iorapd if it's done yet.
             sleep(1000);
         }
@@ -536,19 +555,7 @@
             return false;
         }
 
-        // Wait for the job to finish completely.
-        // Other packages could be compiled in cyclic runs.
-        int currentAttempt = 0;
-        do {
-            String logcatLines = getLogcatSinceTime(logcatTimestamp);
-            if (logcatLines.contains("IorapForwardingService: Finished background job")) {
-                return true;
-            }
-            sleep(1000);
-        } while (currentAttempt++ < IORAP_COMPILE_CMD_TIMEOUT);
-
-        Log.e(TAG, "compileAppForIorap: failed due to jobscheduler timeout.");
-        return false;
+        return true;
     }
 
     /** Save the contents of $(adb shell dumpsys iorapd) to the launch_logs directory. */
@@ -808,11 +815,9 @@
         }
 
         Log.v(TAG, "Purge iorap package: " + packageName);
-        stopIorapd();
         getInstrumentation().getUiAutomation()
                 .executeShellCommand(String.format(IORAP_MAINTENANCE_CMD, packageName));
         Log.v(TAG, "Executed: " + String.format(IORAP_MAINTENANCE_CMD, packageName));
-        startIorapd();
     }
 
     String executeShellCommandWithTempFile(String cmd) {
@@ -892,12 +897,16 @@
             throw new AssertionError(e);
         }
 
-        stopIorapd();
         getInstrumentation().getUiAutomation()
                 .executeShellCommand(String.format("setprop iorapd.perfetto.enable %b", enable));
         getInstrumentation().getUiAutomation()
                 .executeShellCommand(String.format("setprop iorapd.readahead.enable %b", enable));
-        startIorapd();
+        getInstrumentation().getUiAutomation()
+                .executeShellCommand(String.format(
+                        "setprop iorapd.maintenance.min_traces %d", IORAP_COMPILE_MIN_TRACES));
+        // this last command blocks until iorapd refreshes its system properties
+        getInstrumentation().getUiAutomation()
+                .executeShellCommand(String.format("dumpsys iorapd --refresh-properties"));
 
         if (enable) {
             mIorapStatus = IorapStatus.ENABLED;
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java
index b40d022..86c3fa0 100644
--- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java
@@ -53,8 +53,9 @@
                 return true;
             }
 
-            float x = e.getX(0);
+            float x = e.getRawX(0);
             float ratio = (float) x / (float) getWidth() ;
+            ratio = 1-ratio;
 
             LinearLayout.LayoutParams lp1 =
                 new LinearLayout.LayoutParams(0,
@@ -172,10 +173,14 @@
         setContentView(splitView);
     }
 
+    private void addFlags(Intent intent) {
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
+    }
+
     Intent makeSettingsIntent() {
         Intent intent = new Intent();
         intent.setAction(android.provider.Settings.ACTION_SETTINGS);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        addFlags(intent);
         return intent;
     }
 
@@ -183,7 +188,7 @@
         Intent intent = new Intent();
         intent.setAction(Intent.ACTION_MAIN);
         intent.addCategory(Intent.CATEGORY_APP_CONTACTS);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        addFlags(intent);
         return intent;
     }
 
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java
index 03615f3..aa041f2 100644
--- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java
@@ -80,8 +80,10 @@
         } catch (Exception e) {
             // System server died.. oh well
         }
+
         t.reparent(leash, getSurfaceControl())
             .setPosition(leash, 0, 0)
+            .show(leash)
             .apply();
     }
 }
diff --git a/tests/net/common/java/android/net/DependenciesTest.java b/tests/net/common/java/android/net/DependenciesTest.java
new file mode 100644
index 0000000..ac1c28a
--- /dev/null
+++ b/tests/net/common/java/android/net/DependenciesTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.net;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A simple class that tests dependencies to java standard tools from the
+ * Network stack. These tests are not meant to be comprehensive tests of
+ * the relevant APIs : such tests belong in the relevant test suite for
+ * these dependencies. Instead, this just makes sure coverage is present
+ * by calling the methods in the exact way (or a representative way of how)
+ * they are called in the network stack.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DependenciesTest {
+    // Used to in ipmemorystore's RegularMaintenanceJobService to convert
+    // 24 hours into seconds
+    @Test
+    public void testTimeUnit() {
+        final int hours = 24;
+        final long inSeconds = TimeUnit.HOURS.toMillis(hours);
+        assertEquals(inSeconds, hours * 60 * 60 * 1000);
+    }
+
+    private byte[] makeTrivialArray(final int size) {
+        final byte[] src = new byte[size];
+        for (int i = 0; i < size; ++i) {
+            src[i] = (byte) i;
+        }
+        return src;
+    }
+
+    // Used in ApfFilter to find an IP address from a byte array
+    @Test
+    public void testArrays() {
+        final int size = 128;
+        final byte[] src = makeTrivialArray(size);
+
+        // Test copy
+        final int copySize = 16;
+        final int offset = 24;
+        final byte[] expected = new byte[copySize];
+        for (int i = 0; i < copySize; ++i) {
+            expected[i] = (byte) (offset + i);
+        }
+
+        final byte[] copy = Arrays.copyOfRange(src, offset, offset + copySize);
+        assertArrayEquals(expected, copy);
+        assertArrayEquals(new byte[0], Arrays.copyOfRange(src, size, size));
+    }
+
+    // Used mainly in the Dhcp code
+    @Test
+    public void testCopyOf() {
+        final byte[] src = makeTrivialArray(128);
+        final byte[] copy = Arrays.copyOf(src, src.length);
+        assertArrayEquals(src, copy);
+        assertFalse(src == copy);
+
+        assertArrayEquals(new byte[0], Arrays.copyOf(src, 0));
+
+        final int excess = 16;
+        final byte[] biggerCopy = Arrays.copyOf(src, src.length + excess);
+        for (int i = src.length; i < src.length + excess; ++i) {
+            assertEquals(0, biggerCopy[i]);
+        }
+        for (int i = src.length - 1; i >= 0; --i) {
+            assertEquals(src[i], biggerCopy[i]);
+        }
+    }
+
+    // Used mainly in DnsUtils but also various other places
+    @Test
+    public void testAsList() {
+        final int size = 24;
+        final Object[] src = new Object[size];
+        final ArrayList<Object> expected = new ArrayList<>(size);
+        for (int i = 0; i < size; ++i) {
+            final Object o = new Object();
+            src[i] = o;
+            expected.add(o);
+        }
+        assertEquals(expected, Arrays.asList(src));
+    }
+}
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 6e9dc8e..3f8261d 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -17,6 +17,8 @@
 package android.net;
 
 import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED;
+import static android.net.NetworkCapabilities.MAX_TRANSPORT;
+import static android.net.NetworkCapabilities.MIN_TRANSPORT;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
@@ -32,10 +34,12 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
 import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES;
+import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_TEST;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
 import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES;
 
 import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
@@ -45,10 +49,15 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.net.wifi.aware.DiscoverySession;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.aware.WifiAwareNetworkSpecifier;
 import android.os.Build;
+import android.os.Process;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArraySet;
 
@@ -61,6 +70,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mockito;
 
 import java.util.Arrays;
 import java.util.Set;
@@ -74,6 +84,9 @@
     @Rule
     public DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
 
+    private DiscoverySession mDiscoverySession = Mockito.mock(DiscoverySession.class);
+    private PeerHandle mPeerHandle = Mockito.mock(PeerHandle.class);
+
     private boolean isAtLeastR() {
         // BuildCompat.isAtLeastR() is used to check the Android version before releasing Android R.
         // Build.VERSION.SDK_INT > Build.VERSION_CODES.Q is used to check the Android version after
@@ -685,4 +698,238 @@
         assertEquals(TRANSPORT_VPN, transportTypes[2]);
         assertEquals(TRANSPORT_TEST, transportTypes[3]);
     }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testTelephonyNetworkSpecifier() {
+        final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
+        final NetworkCapabilities nc1 = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .setNetworkSpecifier(specifier)
+                .build();
+        assertEquals(specifier, nc1.getNetworkSpecifier());
+        try {
+            final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
+                    .setNetworkSpecifier(specifier)
+                    .build();
+            fail("Must have a single transport type. Without transport type or multiple transport"
+                    + " types is invalid.");
+        } catch (IllegalStateException expected) { }
+    }
+
+    @Test
+    public void testWifiAwareNetworkSpecifier() {
+        final NetworkCapabilities nc = new NetworkCapabilities()
+                .addTransportType(TRANSPORT_WIFI_AWARE);
+        // If NetworkSpecifier is not set, the default value is null.
+        assertNull(nc.getNetworkSpecifier());
+        final WifiAwareNetworkSpecifier specifier = new WifiAwareNetworkSpecifier.Builder(
+                mDiscoverySession, mPeerHandle).build();
+        nc.setNetworkSpecifier(specifier);
+        assertEquals(specifier, nc.getNetworkSpecifier());
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testAdministratorUidsAndOwnerUid() {
+        // Test default owner uid.
+        // If the owner uid is not set, the default value should be Process.INVALID_UID.
+        final NetworkCapabilities nc1 = new NetworkCapabilities.Builder().build();
+        assertEquals(Process.INVALID_UID, nc1.getOwnerUid());
+        // Test setAdministratorUids and getAdministratorUids.
+        final int[] administratorUids = {1001, 10001};
+        final NetworkCapabilities nc2 = new NetworkCapabilities.Builder()
+                .setAdministratorUids(administratorUids)
+                .build();
+        assertTrue(Arrays.equals(administratorUids, nc2.getAdministratorUids()));
+        // Test setOwnerUid and getOwnerUid.
+        // The owner UID must be included in administrator UIDs, or throw IllegalStateException.
+        try {
+            final NetworkCapabilities nc3 = new NetworkCapabilities.Builder()
+                    .setOwnerUid(1001)
+                    .build();
+            fail("The owner UID must be included in administrator UIDs.");
+        } catch (IllegalStateException expected) { }
+        final NetworkCapabilities nc4 = new NetworkCapabilities.Builder()
+                .setAdministratorUids(administratorUids)
+                .setOwnerUid(1001)
+                .build();
+        assertEquals(1001, nc4.getOwnerUid());
+        try {
+            final NetworkCapabilities nc5 = new NetworkCapabilities.Builder()
+                    .setAdministratorUids(null)
+                    .build();
+            fail("Should not set null into setAdministratorUids");
+        } catch (NullPointerException expected) { }
+    }
+
+    @Test
+    public void testLinkBandwidthKbps() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        // The default value of LinkDown/UpstreamBandwidthKbps should be LINK_BANDWIDTH_UNSPECIFIED.
+        assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkDownstreamBandwidthKbps());
+        assertEquals(LINK_BANDWIDTH_UNSPECIFIED, nc.getLinkUpstreamBandwidthKbps());
+        nc.setLinkDownstreamBandwidthKbps(512);
+        nc.setLinkUpstreamBandwidthKbps(128);
+        assertEquals(512, nc.getLinkDownstreamBandwidthKbps());
+        assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps());
+        assertEquals(128, nc.getLinkUpstreamBandwidthKbps());
+        assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps());
+    }
+
+    @Test
+    public void testSignalStrength() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        // The default value of signal strength should be SIGNAL_STRENGTH_UNSPECIFIED.
+        assertEquals(SIGNAL_STRENGTH_UNSPECIFIED, nc.getSignalStrength());
+        nc.setSignalStrength(-80);
+        assertEquals(-80, nc.getSignalStrength());
+        assertNotEquals(-50, nc.getSignalStrength());
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testDeduceRestrictedCapability() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        // Default capabilities don't have restricted capability.
+        assertFalse(nc.deduceRestrictedCapability());
+        // If there is a force restricted capability, then the network capabilities is restricted.
+        nc.addCapability(NET_CAPABILITY_OEM_PAID);
+        nc.addCapability(NET_CAPABILITY_INTERNET);
+        assertTrue(nc.deduceRestrictedCapability());
+        // Except for the force restricted capability, if there is any unrestricted capability in
+        // capabilities, then the network capabilities is not restricted.
+        nc.removeCapability(NET_CAPABILITY_OEM_PAID);
+        nc.addCapability(NET_CAPABILITY_CBS);
+        assertFalse(nc.deduceRestrictedCapability());
+        // Except for the force restricted capability, the network capabilities will only be treated
+        // as restricted when there is no any unrestricted capability.
+        nc.removeCapability(NET_CAPABILITY_INTERNET);
+        assertTrue(nc.deduceRestrictedCapability());
+    }
+
+    private void assertNoTransport(NetworkCapabilities nc) {
+        for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+            assertFalse(nc.hasTransport(i));
+        }
+    }
+
+    // Checks that all transport types from MIN_TRANSPORT to maxTransportType are set and all
+    // transport types from maxTransportType + 1 to MAX_TRANSPORT are not set when positiveSequence
+    // is true. If positiveSequence is false, then the check sequence is opposite.
+    private void checkCurrentTransportTypes(NetworkCapabilities nc, int maxTransportType,
+            boolean positiveSequence) {
+        for (int i = MIN_TRANSPORT; i <= maxTransportType; i++) {
+            if (positiveSequence) {
+                assertTrue(nc.hasTransport(i));
+            } else {
+                assertFalse(nc.hasTransport(i));
+            }
+        }
+        for (int i = MAX_TRANSPORT; i > maxTransportType; i--) {
+            if (positiveSequence) {
+                assertFalse(nc.hasTransport(i));
+            } else {
+                assertTrue(nc.hasTransport(i));
+            }
+        }
+    }
+
+    @Test
+    public void testMultipleTransportTypes() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        assertNoTransport(nc);
+        // Test adding multiple transport types.
+        for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+            nc.addTransportType(i);
+            checkCurrentTransportTypes(nc, i, true /* positiveSequence */);
+        }
+        // Test removing multiple transport types.
+        for (int i = MIN_TRANSPORT; i <= MAX_TRANSPORT; i++) {
+            nc.removeTransportType(i);
+            checkCurrentTransportTypes(nc, i, false /* positiveSequence */);
+        }
+        assertNoTransport(nc);
+        nc.addTransportType(TRANSPORT_WIFI);
+        assertTrue(nc.hasTransport(TRANSPORT_WIFI));
+        assertFalse(nc.hasTransport(TRANSPORT_VPN));
+        nc.addTransportType(TRANSPORT_VPN);
+        assertTrue(nc.hasTransport(TRANSPORT_WIFI));
+        assertTrue(nc.hasTransport(TRANSPORT_VPN));
+        nc.removeTransportType(TRANSPORT_WIFI);
+        assertFalse(nc.hasTransport(TRANSPORT_WIFI));
+        assertTrue(nc.hasTransport(TRANSPORT_VPN));
+        nc.removeTransportType(TRANSPORT_VPN);
+        assertFalse(nc.hasTransport(TRANSPORT_WIFI));
+        assertFalse(nc.hasTransport(TRANSPORT_VPN));
+        assertNoTransport(nc);
+    }
+
+    @Test
+    public void testAddAndRemoveTransportType() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        try {
+            nc.addTransportType(-1);
+            fail("Should not set invalid transport type into addTransportType");
+        } catch (IllegalArgumentException expected) { }
+        try {
+            nc.removeTransportType(-1);
+            fail("Should not set invalid transport type into removeTransportType");
+        } catch (IllegalArgumentException e) { }
+    }
+
+    private class TestTransportInfo implements TransportInfo {
+        TestTransportInfo() {
+        }
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testBuilder() {
+        final int ownerUid = 1001;
+        final int signalStrength = -80;
+        final int requestUid = 10100;
+        final int[] administratorUids = {ownerUid, 10001};
+        final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier(1);
+        final TestTransportInfo transportInfo = new TestTransportInfo();
+        final String ssid = "TEST_SSID";
+        final String packageName = "com.google.test.networkcapabilities";
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .addTransportType(TRANSPORT_CELLULAR)
+                .removeTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_EIMS)
+                .addCapability(NET_CAPABILITY_CBS)
+                .removeCapability(NET_CAPABILITY_CBS)
+                .setAdministratorUids(administratorUids)
+                .setOwnerUid(ownerUid)
+                .setLinkDownstreamBandwidthKbps(512)
+                .setLinkUpstreamBandwidthKbps(128)
+                .setNetworkSpecifier(specifier)
+                .setTransportInfo(transportInfo)
+                .setSignalStrength(signalStrength)
+                .setSsid(ssid)
+                .setRequestorUid(requestUid)
+                .setRequestorPackageName(packageName)
+                .build();
+        assertEquals(1, nc.getTransportTypes().length);
+        assertEquals(TRANSPORT_WIFI, nc.getTransportTypes()[0]);
+        assertTrue(nc.hasCapability(NET_CAPABILITY_EIMS));
+        assertFalse(nc.hasCapability(NET_CAPABILITY_CBS));
+        assertTrue(Arrays.equals(administratorUids, nc.getAdministratorUids()));
+        assertEquals(ownerUid, nc.getOwnerUid());
+        assertEquals(512, nc.getLinkDownstreamBandwidthKbps());
+        assertNotEquals(128, nc.getLinkDownstreamBandwidthKbps());
+        assertEquals(128, nc.getLinkUpstreamBandwidthKbps());
+        assertNotEquals(512, nc.getLinkUpstreamBandwidthKbps());
+        assertEquals(specifier, nc.getNetworkSpecifier());
+        assertEquals(transportInfo, nc.getTransportInfo());
+        assertEquals(signalStrength, nc.getSignalStrength());
+        assertNotEquals(-50, nc.getSignalStrength());
+        assertEquals(ssid, nc.getSsid());
+        assertEquals(requestUid, nc.getRequestorUid());
+        assertEquals(packageName, nc.getRequestorPackageName());
+        // Cannot assign null into NetworkCapabilities.Builder
+        try {
+            final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(null);
+            fail("Should not set null into NetworkCapabilities.Builder");
+        } catch (NullPointerException expected) { }
+        assertEquals(nc, new NetworkCapabilities.Builder(nc).build());
+    }
 }
diff --git a/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt b/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt
index 9119d62..7b22e45 100644
--- a/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt
+++ b/tests/net/common/java/android/net/netstats/NetworkStatsApiTest.kt
@@ -31,7 +31,6 @@
 import android.os.Build
 import androidx.test.filters.SmallTest
 import com.android.testutils.DevSdkIgnoreRule
-import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
 import com.android.testutils.assertFieldCountEquals
 import com.android.testutils.assertNetworkStatsEquals
 import com.android.testutils.assertParcelingIsLossless
@@ -47,70 +46,22 @@
 class NetworkStatsApiTest {
     @Rule
     @JvmField
-    val ignoreRule = DevSdkIgnoreRule()
+    val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
 
     private val testStatsEmpty = NetworkStats(0L, 0)
 
+    // Note that these variables need to be initialized outside of constructor, initialize
+    // here with methods that don't exist in Q devices will result in crash.
+
     // stats1 and stats2 will have some entries with common keys, which are expected to
     // be merged if performing add on these 2 stats.
-    private val testStats1 = NetworkStats(0L, 0)
-            // Entries which only appear in set1.
-            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
-            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
-                    METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8))
-            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
-                    METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2))
-            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 37, 52, 1, 10, 4))
-            // Entries which are common for set1 and set2.
-            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5))
-            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 17, 2, 11, 1, 0))
-            .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 40, 1, 0, 0, 8))
-            .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 1, 6, 2, 0))
-
-    private val testStats2 = NetworkStats(0L, 0)
-            // Entries which are common for set1 and set2.
-            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1))
-            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45))
-            .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7))
-            .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0))
-            // Entry which only appears in set2.
-            .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
+    private lateinit var testStats1: NetworkStats
+    private lateinit var testStats2: NetworkStats
 
     // This is a result of adding stats1 and stats2, while the merging of common key items is
     // subject to test later, this should not be initialized with for a loop to add stats1
     // and stats2 above.
-    private val testStats3 = NetworkStats(0L, 9)
-            // Entries which are unique either in stats1 or stats2.
-            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5))
-            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
-                    METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8))
-            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
-                    METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2))
-            .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
-            // Entries which are common for stats1 and stats2 are being merged.
-            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
-            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 20, 17, 13, 32, 1))
-            .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 50, 113, 11, 11, 49))
-            .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 51, 3, 3, 4, 15))
-            .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 7, 4, 8, 3, 0))
+    private lateinit var testStats3: NetworkStats
 
     companion object {
         private const val TEST_IFACE = "test0"
@@ -120,13 +71,67 @@
 
     @Before
     fun setUp() {
+        testStats1 = NetworkStats(0L, 0)
+                // Entries which only appear in set1.
+                .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
+                .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8))
+                .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                        METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2))
+                .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 37, 52, 1, 10, 4))
+                // Entries which are common for set1 and set2.
+                .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5))
+                .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 17, 2, 11, 1, 0))
+                .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 40, 1, 0, 0, 8))
+                .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 1, 6, 2, 0))
         assertEquals(8, testStats1.size())
+
+        testStats2 = NetworkStats(0L, 0)
+                // Entries which are common for set1 and set2.
+                .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1))
+                .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45))
+                .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7))
+                .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0))
+                // Entry which only appears in set2.
+                .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
         assertEquals(5, testStats2.size())
+
+        testStats3 = NetworkStats(0L, 9)
+                // Entries which are unique either in stats1 or stats2.
+                .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 101, 2, 103, 4, 5))
+                .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_YES, DEFAULT_NETWORK_NO, 31, 7, 24, 5, 8))
+                .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                        METERED_YES, ROAMING_NO, DEFAULT_NETWORK_NO, 25, 3, 47, 8, 2))
+                .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
+                // Entries which are common for stats1 and stats2 are being merged.
+                .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_YES, 20, 3, 57, 40, 3))
+                .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 20, 17, 13, 32, 1))
+                .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 50, 113, 11, 11, 49))
+                .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 51, 3, 3, 4, 15))
+                .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 7, 4, 8, 3, 0))
         assertEquals(9, testStats3.size())
     }
 
     @Test
-    @IgnoreUpTo(Build.VERSION_CODES.Q)
     fun testAddEntry() {
         val expectedEntriesInStats2 = arrayOf(
                 Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
@@ -156,7 +161,6 @@
     }
 
     @Test
-    @IgnoreUpTo(Build.VERSION_CODES.Q)
     fun testAdd() {
         var stats = NetworkStats(0L, 0)
         assertNetworkStatsEquals(testStatsEmpty, stats)
@@ -168,7 +172,6 @@
     }
 
     @Test
-    @IgnoreUpTo(Build.VERSION_CODES.Q)
     fun testParcelUnparcel() {
         assertParcelingIsLossless(testStatsEmpty)
         assertParcelingIsLossless(testStats1)
@@ -177,7 +180,6 @@
     }
 
     @Test
-    @IgnoreUpTo(Build.VERSION_CODES.Q)
     fun testDescribeContents() {
         assertEquals(0, testStatsEmpty.describeContents())
         assertEquals(0, testStats1.describeContents())
@@ -186,7 +188,6 @@
     }
 
     @Test
-    @IgnoreUpTo(Build.VERSION_CODES.Q)
     fun testSubtract() {
         // STATS3 - STATS2 = STATS1
         assertNetworkStatsEquals(testStats1, testStats3.subtract(testStats2))
@@ -195,7 +196,6 @@
     }
 
     @Test
-    @IgnoreUpTo(Build.VERSION_CODES.Q)
     fun testMethodsDontModifyReceiver() {
         listOf(testStatsEmpty, testStats1, testStats2, testStats3).forEach {
             val origStats = it.clone()
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index 4c2a984..737665f 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -17,6 +17,7 @@
 package com.android.framework.permission.tests;
 
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import android.os.Binder;
 import android.os.RemoteException;
@@ -27,6 +28,8 @@
 
 import junit.framework.TestCase;
 
+import org.junit.Test;
+
 /**
  * TODO: Remove this. This is only a placeholder, need to implement this.
  */
@@ -53,7 +56,7 @@
         }
 
         try {
-            mWm.addWindowToken(null, 0, DEFAULT_DISPLAY);
+            mWm.addWindowToken(null, TYPE_APPLICATION, DEFAULT_DISPLAY);
             fail("IWindowManager.addWindowToken did not throw SecurityException as"
                     + " expected");
         } catch (SecurityException e) {
@@ -63,16 +66,6 @@
         }
 
         try {
-            mWm.removeWindowToken(null, DEFAULT_DISPLAY);
-            fail("IWindowManager.removeWindowToken did not throw SecurityException as"
-                    + " expected");
-        } catch (SecurityException e) {
-            // expected
-        } catch (RemoteException e) {
-            fail("Unexpected remote exception");
-        }
-
-        try {
             mWm.prepareAppTransition(0, false);
             fail("IWindowManager.prepareAppTransition did not throw SecurityException as"
                     + " expected");
@@ -182,4 +175,29 @@
             fail("Unexpected remote exception");
         }
     }
+
+    @Test
+    public void testADD_WINDOW_TOKEN_WITH_OPTIONS() {
+        // Verify if addWindowTokenWithOptions throw SecurityException for privileged window type.
+        try {
+            mWm.addWindowTokenWithOptions(null, TYPE_APPLICATION, DEFAULT_DISPLAY, null, "");
+            fail("IWindowManager.addWindowTokenWithOptions did not throw SecurityException as"
+                    + " expected");
+        } catch (SecurityException e) {
+            // expected
+        } catch (RemoteException e) {
+            fail("Unexpected remote exception");
+        }
+
+        // Verify if addWindowTokenWithOptions throw SecurityException for null packageName.
+        try {
+            mWm.addWindowTokenWithOptions(null, TYPE_APPLICATION, DEFAULT_DISPLAY, null, null);
+            fail("IWindowManager.addWindowTokenWithOptions did not throw SecurityException as"
+                    + " expected");
+        } catch (SecurityException e) {
+            // expected
+        } catch (RemoteException e) {
+            fail("Unexpected remote exception");
+        }
+    }
 }
diff --git a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
index 3026e0b..0dd45ba 100644
--- a/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
+++ b/tests/utils/testutils/java/com/android/server/wm/test/filters/FrameworksTestsFilter.java
@@ -47,7 +47,8 @@
             "android.view.InsetsSourceConsumerTest",
             "android.view.InsetsStateTest",
             "android.view.WindowMetricsTest",
-            "android.view.PendingInsetsControllerTest"
+            "android.view.PendingInsetsControllerTest",
+            "android.app.WindowContextTest"
     };
 
     public FrameworksTestsFilter(Bundle testArgs) {
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index d56f2be..b513463e 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -160,7 +160,7 @@
     int exclusiveField = 0;
     int defaultState = INT_MAX;
     int triggerStateReset = INT_MAX;
-    bool nested;
+    bool nested = true;
 
     int uidField = 0;
 
diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp
index 23a0f72..5fe9498 100644
--- a/tools/stats_log_api_gen/atoms_info_writer.cpp
+++ b/tools/stats_log_api_gen/atoms_info_writer.cpp
@@ -42,7 +42,6 @@
     fprintf(out,
             "  const static std::set<int> "
             "kTruncatingTimestampAtomBlackList;\n");
-    fprintf(out, "  const static std::map<int, int> kAtomsWithUidField;\n");
     fprintf(out, "  const static std::set<int> kAtomsWithAttributionChain;\n");
     fprintf(out,
             "  const static std::map<int, StateAtomFieldOptions> "
@@ -101,28 +100,6 @@
     fprintf(out, "};\n");
     fprintf(out, "\n");
 
-    fprintf(out, "static std::map<int, int> getAtomUidField() {\n");
-    fprintf(out, "    std::map<int, int> uidField;\n");
-    for (AtomDeclSet::const_iterator atomIt = atoms.decls.begin(); atomIt != atoms.decls.end();
-         atomIt++) {
-        if ((*atomIt)->uidField == 0) {
-            continue;
-        }
-        fprintf(out,
-                "\n    // Adding uid field for atom "
-                "(%d)%s\n",
-                (*atomIt)->code, (*atomIt)->name.c_str());
-        fprintf(out, "    uidField[%d /* %s */] = %d;\n", (*atomIt)->code,
-                make_constant_name((*atomIt)->name).c_str(), (*atomIt)->uidField);
-    }
-
-    fprintf(out, "    return uidField;\n");
-    fprintf(out, "};\n");
-
-    fprintf(out,
-            "const std::map<int, int> AtomsInfo::kAtomsWithUidField = "
-            "getAtomUidField();\n");
-
     fprintf(out,
             "static std::map<int, StateAtomFieldOptions> "
             "getStateAtomFieldOptions() {\n");
diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt
index eeb006e..f0333c9 100644
--- a/wifi/jarjar-rules.txt
+++ b/wifi/jarjar-rules.txt
@@ -10,11 +10,17 @@
 rule android.net.ip.IpClientCallbacks* com.android.server.x.wifi.net.ip.IpClientCallbacks@1
 rule android.net.ip.IpClientManager* com.android.server.x.wifi.net.ip.IpClientManager@1
 rule android.net.ip.IpClientUtil* com.android.server.x.wifi.net.ip.IpClientUtil@1
+rule android.net.ipmemorystore.OnBlobRetrievedListener* com.android.server.x.wifi.net.ipmemorystore.OnBlobRetrievedListener@1
+rule android.net.ipmemorystore.OnStatusListener* com.android.server.x.wifi.net.ipmemorystore.OnStatusListener@1
+rule android.net.ipmemorystore.StatusParcelable* @0
+rule android.net.ipmemorystore.Status* com.android.server.x.wifi.net.ipmemorystore.Status@1
+rule android.net.networkstack.ModuleNetworkStackClient* com.android.server.x.wifi.net.networkstack.ModuleNetworkStackClient@1
+rule android.net.networkstack.NetworkStackClientBase* com.android.server.x.wifi.net.networkstack.NetworkStackClientBase@1
 rule android.net.shared.InetAddressUtils* com.android.server.x.wifi.net.shared.InetAddressUtils@1
 rule android.net.shared.InitialConfiguration* com.android.server.x.wifi.net.shared.InitialConfiguration@1
 rule android.net.shared.IpConfigurationParcelableUtil* com.android.server.x.wifi.net.shared.IpConfigurationParcelableUtil@1
+rule android.net.shared.Layer2Information* com.android.server.x.wifi.net.shared.Layer2Information@1
 rule android.net.shared.LinkPropertiesParcelableUtil* com.android.server.x.wifi.net.shared.LinkPropertiesParcelableUtil@1
-rule android.net.shared.ParcelableUtil* com.android.server.x.wifi.net.shared.ParcelableUtil@1
 rule android.net.shared.NetdUtils* com.android.server.x.wifi.net.shared.NetdUtils@1
 rule android.net.shared.NetworkMonitorUtils* com.android.server.x.wifi.net.shared.NetworkMonitorUtils@1
 rule android.net.shared.ParcelableUtil* com.android.server.x.wifi.net.shared.ParcelableUtil@1
@@ -28,6 +34,8 @@
 rule android.net.util.NetUtils* com.android.server.x.wifi.net.util.NetUtils@1
 rule android.net.util.IpUtils* com.android.server.x.wifi.net.util.IpUtils@1
 
+rule androidx.annotation.** com.android.server.x.wifi.androidx.annotation.@1
+
 # We don't jar-jar the entire package because, we still use some classes (like
 # AsyncChannel in com.android.internal.util) from these packages which are not
 # inside our jar (currently in framework.jar, but will be in wifisdk.jar in the future).
@@ -53,6 +61,13 @@
 rule com.android.internal.messages.SystemMessageProto* com.android.server.x.wifi.messages.SystemMessageProto@1
 # Use our statically linked PlatformProperties library
 rule android.sysprop.** com.android.server.x.wifi.sysprop.@1
+# Use our statically linked HIDL stubs
+rule android.hardware.** com.android.server.x.wifi.hardware.@1
+rule android.hidl.** com.android.server.x.wifi.hidl.@1
+# Use our statically linked ksoap2
+rule org.ksoap2.** com.android.server.x.wifi.ksoap2.@1
+# Use our statically linked nanohttpd
+rule fi.iki.elonen.** com.android.server.x.wifi.elonen.@1
 
 # used by both framework-wifi and wifi-service
 rule android.content.pm.BaseParceledListSlice* android.x.net.wifi.util.BaseParceledListSlice@1
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index f1be8b2..6c8dc00 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -179,6 +179,8 @@
     /**
      * Reason code if one or more of the network suggestions added already exists in platform's
      * database.
+     * Note: this code will not be returned with Android 11 as in-place modification is allowed,
+     * please check {@link #addNetworkSuggestions(List)}.
      * @see WifiNetworkSuggestion#equals(Object)
      */
     public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE = 3;
@@ -186,6 +188,8 @@
     /**
      * Reason code if the number of network suggestions provided by the app crosses the max
      * threshold set per app.
+     * The framework will reject all suggestions provided by {@link #addNetworkSuggestions(List)} if
+     * the total size exceeds the limit.
      * @see #getMaxNumberOfNetworkSuggestionsPerApp()
      */
     public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP = 4;
@@ -193,21 +197,27 @@
     /**
      * Reason code if one or more of the network suggestions removed does not exist in platform's
      * database.
+     * The framework won't remove any suggestions if one or more of suggestions provided
+     * by {@link #removeNetworkSuggestions(List)} does not exist in database.
+     * @see WifiNetworkSuggestion#equals(Object)
      */
     public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID = 5;
 
     /**
      * Reason code if one or more of the network suggestions added is not allowed.
-     *
+     * The framework will reject all suggestions provided by {@link #addNetworkSuggestions(List)}
+     * if one or more of them is not allowed.
      * This error may be caused by suggestion is using SIM-based encryption method, but calling app
      * is not carrier privileged.
      */
     public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED = 6;
 
     /**
-     * Reason code if one or more of the network suggestions added is invalid.
-     *
-     * Please user {@link WifiNetworkSuggestion.Builder} to create network suggestions.
+     * Reason code if one or more of the network suggestions added is invalid. Framework will reject
+     * all the suggestions in the list.
+     * The framework will reject all suggestions provided by {@link #addNetworkSuggestions(List)}
+     * if one or more of them is invalid.
+     * Please use {@link WifiNetworkSuggestion.Builder} to create network suggestions.
      */
     public static final int STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID = 7;
 
@@ -1887,7 +1897,7 @@
      * <li> If user reset network settings, all added suggestions will be discarded. Apps can use
      * {@link #getNetworkSuggestions()} to check if their suggestions are in the device.</li>
      * <li> In-place modification of existing suggestions are allowed.
-     * <li>If the provided suggestions includes any previously provided suggestions by the app,
+     * <li> If the provided suggestions include any previously provided suggestions by the app,
      * previous suggestions will be updated.</li>
      * <li>If one of the provided suggestions marks a previously unmetered suggestion as metered and
      * the device is currently connected to that suggested network, then the device will disconnect