Snap for 5434517 from 2fec69813c3d118585f03b32654cf3d8edb2bb1a to qt-release

Change-Id: Ib6d45da0b3531f90d5dda861b28dfb88665fe96c
diff --git a/Android.bp b/Android.bp
index b9b1bd8..5b8e6e1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -375,6 +375,7 @@
         "core/java/android/view/IDisplayFoldListener.aidl",
         "core/java/android/view/IGraphicsStats.aidl",
         "core/java/android/view/IGraphicsStatsCallback.aidl",
+        "core/java/android/view/IInputMonitorHost.aidl",
         "core/java/android/view/IInputFilter.aidl",
         "core/java/android/view/IInputFilterHost.aidl",
         "core/java/android/view/IOnKeyguardExitResult.aidl",
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 2fdba0a..c121bd9 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -27,6 +27,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
@@ -130,6 +131,47 @@
         }
     }
 
+    /** Tests switching to an already-created, but no-longer-running, user. */
+    @Test
+    public void switchUser_stopped() throws Exception {
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            final int startUser = mAm.getCurrentUser();
+            final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true);
+            final CountDownLatch latch = new CountDownLatch(1);
+            registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch, testUser);
+            mRunner.resumeTiming();
+
+            mAm.switchUser(testUser);
+            boolean success = latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
+
+            mRunner.pauseTiming();
+            attestTrue("Failed to achieve 2nd ACTION_USER_UNLOCKED for user " + testUser, success);
+            switchUser(startUser);
+            removeUser(testUser);
+            mRunner.resumeTiming();
+        }
+    }
+
+    /** Tests switching to an already-created already-running non-owner user. */
+    @Test
+    public void switchUser_running() throws Exception {
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            final int startUser = mAm.getCurrentUser();
+            final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ false);
+            mRunner.resumeTiming();
+
+            switchUser(testUser);
+
+            mRunner.pauseTiming();
+            attestTrue("Failed to switch to user " + testUser, mAm.isUserRunning(testUser));
+            switchUser(startUser);
+            removeUser(testUser);
+            mRunner.resumeTiming();
+        }
+    }
+
     @Test
     public void stopUser() throws Exception {
         while (mRunner.keepRunning()) {
@@ -188,6 +230,34 @@
         }
     }
 
+    /** Tests starting an already-created, but no-longer-running, profile. */
+    @Test
+    public void managedProfileUnlock_stopped() throws Exception {
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            final UserInfo userInfo = mUm.createProfileForUser("TestUser",
+                    UserInfo.FLAG_MANAGED_PROFILE, mAm.getCurrentUser());
+            // Start the profile initially, then stop it. Similar to setQuietModeEnabled.
+            final CountDownLatch latch1 = new CountDownLatch(1);
+            registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch1, userInfo.id);
+            mIam.startUserInBackground(userInfo.id);
+            latch1.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
+            stopUser(userInfo.id, true);
+
+            // Now we restart the profile.
+            final CountDownLatch latch2 = new CountDownLatch(1);
+            registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch2, userInfo.id);
+            mRunner.resumeTiming();
+
+            mIam.startUserInBackground(userInfo.id);
+            latch2.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
+
+            mRunner.pauseTiming();
+            removeUser(userInfo.id);
+            mRunner.resumeTiming();
+        }
+    }
+
     @Test
     public void ephemeralUserStopped() throws Exception {
         while (mRunner.keepRunning()) {
@@ -262,6 +332,35 @@
         latch.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS);
     }
 
+    /**
+     * Creates a user and waits for its ACTION_USER_UNLOCKED.
+     * Then switches to back to the original user and waits for its switchUser() to finish.
+     *
+     * @param stopNewUser whether to stop the new user after switching to otherUser.
+     * @return userId of the newly created user.
+     */
+    private int initializeNewUserAndSwitchBack(boolean stopNewUser) throws Exception {
+        final int origUser = mAm.getCurrentUser();
+        // First, create and switch to testUser, waiting for its ACTION_USER_UNLOCKED
+        final int testUser = mUm.createUser("TestUser", 0).id;
+        final CountDownLatch latch1 = new CountDownLatch(1);
+        registerBroadcastReceiver(Intent.ACTION_USER_UNLOCKED, latch1, testUser);
+        mAm.switchUser(testUser);
+        attestTrue("Failed to achieve initial ACTION_USER_UNLOCKED for user " + testUser,
+                latch1.await(TIMEOUT_IN_SECOND, TimeUnit.SECONDS));
+
+        // Second, switch back to origUser, waiting merely for switchUser() to finish
+        switchUser(origUser);
+        attestTrue("Didn't switch back to user, " + origUser, origUser == mAm.getCurrentUser());
+
+        if (stopNewUser) {
+            stopUser(testUser, true);
+            attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
+        }
+
+        return testUser;
+    }
+
     private void registerUserSwitchObserver(final CountDownLatch switchLatch,
             final CountDownLatch bootCompleteLatch, final int userId) throws Exception {
         ActivityManager.getService().registerUserSwitchObserver(
@@ -313,4 +412,14 @@
             mUsersToRemove.add(userId);
         }
     }
+
+    private void attestTrue(String message, boolean attestion) {
+        if (!attestion) {
+            Log.w(TAG, message);
+        }
+    }
+
+    private void attestFalse(String message, boolean attestion) {
+        attestTrue(message, !attestion);
+    }
 }
diff --git a/api/current.txt b/api/current.txt
index 1394ba3..f78dfde 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11452,6 +11452,7 @@
     method public long getSize();
     method public int getStagedSessionErrorCode();
     method @NonNull public String getStagedSessionErrorMessage();
+    method public long getUpdatedMillis();
     method @NonNull public android.os.UserHandle getUser();
     method public boolean isActive();
     method public boolean isCommitted();
@@ -34275,6 +34276,7 @@
     ctor public Binder(@Nullable String);
     method public void attachInterface(@Nullable android.os.IInterface, @Nullable String);
     method public static final long clearCallingIdentity();
+    method public static final long clearCallingWorkSource();
     method public void dump(@NonNull java.io.FileDescriptor, @Nullable String[]);
     method protected void dump(@NonNull java.io.FileDescriptor, @NonNull java.io.PrintWriter, @Nullable String[]);
     method public void dumpAsync(@NonNull java.io.FileDescriptor, @Nullable String[]);
@@ -34283,6 +34285,7 @@
     method public static final int getCallingUid();
     method public static final int getCallingUidOrThrow();
     method @NonNull public static final android.os.UserHandle getCallingUserHandle();
+    method public static final int getCallingWorkSourceUid();
     method @Nullable public String getInterfaceDescriptor();
     method public boolean isBinderAlive();
     method public static final void joinThreadPool();
@@ -34291,6 +34294,8 @@
     method public boolean pingBinder();
     method @Nullable public android.os.IInterface queryLocalInterface(@NonNull String);
     method public static final void restoreCallingIdentity(long);
+    method public static final void restoreCallingWorkSource(long);
+    method public static final long setCallingWorkSourceUid(int);
     method public final boolean transact(int, @NonNull android.os.Parcel, @Nullable android.os.Parcel, int) throws android.os.RemoteException;
     method public boolean unlinkToDeath(@NonNull android.os.IBinder.DeathRecipient, int);
   }
@@ -37069,12 +37074,6 @@
     field public static final String CACHED_NUMBER_TYPE = "numbertype";
     field public static final String CACHED_PHOTO_ID = "photo_id";
     field public static final String CACHED_PHOTO_URI = "photo_uri";
-    field public static final String CALL_ID_APP_NAME = "call_id_app_name";
-    field public static final String CALL_ID_DESCRIPTION = "call_id_description";
-    field public static final String CALL_ID_DETAILS = "call_id_details";
-    field public static final String CALL_ID_NAME = "call_id_name";
-    field public static final String CALL_ID_NUISANCE_CONFIDENCE = "call_id_nuisance_confidence";
-    field public static final String CALL_ID_PACKAGE_NAME = "call_id_package_name";
     field public static final String CALL_SCREENING_APP_NAME = "call_screening_app_name";
     field public static final String CALL_SCREENING_COMPONENT_NAME = "call_screening_component_name";
     field public static final android.net.Uri CONTENT_FILTER_URI;
@@ -43257,7 +43256,6 @@
     method public android.telecom.PhoneAccountHandle getAccountHandle();
     method public int getCallCapabilities();
     method public int getCallDirection();
-    method @Nullable public android.telecom.CallIdentification getCallIdentification();
     method public int getCallProperties();
     method public String getCallerDisplayName();
     method public int getCallerDisplayNamePresentation();
@@ -43339,34 +43337,6 @@
     field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
   }
 
-  public final class CallIdentification implements android.os.Parcelable {
-    method public int describeContents();
-    method @NonNull public CharSequence getCallScreeningAppName();
-    method @NonNull public String getCallScreeningPackageName();
-    method @Nullable public CharSequence getDescription();
-    method @Nullable public CharSequence getDetails();
-    method @Nullable public CharSequence getName();
-    method public int getNuisanceConfidence();
-    method @Nullable public android.graphics.drawable.Icon getPhoto();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final int CONFIDENCE_LIKELY_NOT_NUISANCE = -1; // 0xffffffff
-    field public static final int CONFIDENCE_LIKELY_NUISANCE = 1; // 0x1
-    field public static final int CONFIDENCE_NOT_NUISANCE = -2; // 0xfffffffe
-    field public static final int CONFIDENCE_NUISANCE = 2; // 0x2
-    field public static final int CONFIDENCE_UNKNOWN = 0; // 0x0
-    field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallIdentification> CREATOR;
-  }
-
-  public static final class CallIdentification.Builder {
-    ctor public CallIdentification.Builder();
-    method @NonNull public android.telecom.CallIdentification build();
-    method @NonNull public android.telecom.CallIdentification.Builder setDescription(@Nullable CharSequence);
-    method @NonNull public android.telecom.CallIdentification.Builder setDetails(@Nullable CharSequence);
-    method @NonNull public android.telecom.CallIdentification.Builder setName(@Nullable CharSequence);
-    method @NonNull public android.telecom.CallIdentification.Builder setNuisanceConfidence(int);
-    method @NonNull public android.telecom.CallIdentification.Builder setPhoto(@Nullable android.graphics.drawable.Icon);
-  }
-
   public abstract class CallRedirectionService extends android.app.Service {
     ctor public CallRedirectionService();
     method public final void cancelCall();
@@ -43382,17 +43352,7 @@
     ctor public CallScreeningService();
     method public android.os.IBinder onBind(android.content.Intent);
     method public abstract void onScreenCall(@NonNull android.telecom.Call.Details);
-    method public final void provideCallIdentification(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallIdentification);
     method public final void respondToCall(@NonNull android.telecom.Call.Details, @NonNull android.telecom.CallScreeningService.CallResponse);
-    field public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED = "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED";
-    field public static final int CALL_DURATION_LONG = 4; // 0x4
-    field public static final int CALL_DURATION_MEDIUM = 3; // 0x3
-    field public static final int CALL_DURATION_SHORT = 2; // 0x2
-    field public static final int CALL_DURATION_VERY_SHORT = 1; // 0x1
-    field public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
-    field public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE";
-    field public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE";
-    field public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE";
     field public static final String SERVICE_INTERFACE = "android.telecom.CallScreeningService";
   }
 
@@ -44001,7 +43961,6 @@
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVoiceMailNumber(android.telecom.PhoneAccountHandle, String);
     method @RequiresPermission(anyOf={android.Manifest.permission.CALL_PHONE, android.Manifest.permission.MANAGE_OWN_CALLS}) public void placeCall(android.net.Uri, android.os.Bundle);
     method public void registerPhoneAccount(android.telecom.PhoneAccount);
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void reportNuisanceCallStatus(@NonNull android.net.Uri, boolean);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public void showInCallScreen(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void silenceRinger();
     method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle);
@@ -45150,6 +45109,7 @@
     method public boolean canChangeDtmfToneLength();
     method @Nullable public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle);
     method public android.telephony.TelephonyManager createForSubscriptionId(int);
+    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean doesSwitchMultiSimConfigTriggerReboot();
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo();
     method public int getCallState();
     method public int getCardIdForDefaultEuicc();
diff --git a/api/system-current.txt b/api/system-current.txt
index b419851..3f74596 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1462,7 +1462,7 @@
     method @Nullable public String getCategory();
     method @NonNull public String getPackageName();
     method @Nullable public String getTargetOverlayableName();
-    method @Nullable public String getTargetPackageName();
+    method @NonNull public String getTargetPackageName();
     method public int getUserId();
     method public boolean isEnabled();
     method public void writeToParcel(android.os.Parcel, int);
@@ -4232,13 +4232,19 @@
     method @Nullable public java.net.InetAddress getGateway();
     method @Nullable public android.net.LinkAddress getIpAddress();
     method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(@Nullable String);
-    method public void setDomains(@Nullable String);
-    method public void setGateway(@Nullable java.net.InetAddress);
-    method public void setIpAddress(@Nullable android.net.LinkAddress);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
   }
 
+  public static final class StaticIpConfiguration.Builder {
+    ctor public StaticIpConfiguration.Builder();
+    method @NonNull public android.net.StaticIpConfiguration build();
+    method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable<java.net.InetAddress>);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress);
+  }
+
   public class TrafficStats {
     method public static void setThreadStatsTagApp();
     method public static void setThreadStatsTagBackup();
@@ -4288,37 +4294,6 @@
 
 }
 
-package android.net.captiveportal {
-
-  public final class CaptivePortalProbeResult {
-    ctor public CaptivePortalProbeResult(int);
-    ctor public CaptivePortalProbeResult(int, @Nullable String, @Nullable String);
-    ctor public CaptivePortalProbeResult(int, @Nullable String, @Nullable String, @Nullable android.net.captiveportal.CaptivePortalProbeSpec);
-    method public boolean isFailed();
-    method public boolean isPartialConnectivity();
-    method public boolean isPortal();
-    method public boolean isSuccessful();
-    field @NonNull public static final android.net.captiveportal.CaptivePortalProbeResult FAILED;
-    field public static final int FAILED_CODE = 599; // 0x257
-    field public static final android.net.captiveportal.CaptivePortalProbeResult PARTIAL;
-    field public static final int PORTAL_CODE = 302; // 0x12e
-    field @NonNull public static final android.net.captiveportal.CaptivePortalProbeResult SUCCESS;
-    field public static final int SUCCESS_CODE = 204; // 0xcc
-    field @Nullable public final String detectUrl;
-    field @Nullable public final android.net.captiveportal.CaptivePortalProbeSpec probeSpec;
-    field @Nullable public final String redirectUrl;
-  }
-
-  public abstract class CaptivePortalProbeSpec {
-    method @NonNull public String getEncodedSpec();
-    method @NonNull public abstract android.net.captiveportal.CaptivePortalProbeResult getResult(int, @Nullable String);
-    method @NonNull public java.net.URL getUrl();
-    method @NonNull public static java.util.Collection<android.net.captiveportal.CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(@NonNull String);
-    method @Nullable public static android.net.captiveportal.CaptivePortalProbeSpec parseSpecOrNull(@Nullable String);
-  }
-
-}
-
 package android.net.metrics {
 
   public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
@@ -5168,10 +5143,6 @@
   }
 
   public class Binder implements android.os.IBinder {
-    method public static final long clearCallingWorkSource();
-    method public static final int getCallingWorkSourceUid();
-    method public static final void restoreCallingWorkSource(long);
-    method public static final long setCallingWorkSourceUid(int);
     method public static void setProxyTransactListener(@Nullable android.os.Binder.ProxyTransactListener);
   }
 
@@ -8092,7 +8063,6 @@
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRadioOn();
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isRebootRequiredForModemConfigChange();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isVideoCallingEnabled();
     method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle);
diff --git a/api/test-current.txt b/api/test-current.txt
index cb2dc07..67a26f3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -716,6 +716,11 @@
 
 package android.content.res {
 
+  public final class AssetManager implements java.lang.AutoCloseable {
+    method @NonNull public String[] getApkPaths();
+    method @Nullable public java.util.Map<java.lang.String,java.lang.String> getOverlayableMap(String);
+  }
+
   public final class Configuration implements java.lang.Comparable<android.content.res.Configuration> android.os.Parcelable {
     field public int assetsSeq;
     field public final android.app.WindowConfiguration windowConfiguration;
@@ -1320,13 +1325,19 @@
     method @Nullable public java.net.InetAddress getGateway();
     method @Nullable public android.net.LinkAddress getIpAddress();
     method @NonNull public java.util.List<android.net.RouteInfo> getRoutes(@Nullable String);
-    method public void setDomains(@Nullable String);
-    method public void setGateway(@Nullable java.net.InetAddress);
-    method public void setIpAddress(@Nullable android.net.LinkAddress);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.StaticIpConfiguration> CREATOR;
   }
 
+  public static final class StaticIpConfiguration.Builder {
+    ctor public StaticIpConfiguration.Builder();
+    method @NonNull public android.net.StaticIpConfiguration build();
+    method @NonNull public android.net.StaticIpConfiguration.Builder setDnsServers(@NonNull Iterable<java.net.InetAddress>);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setDomains(@Nullable String);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setGateway(@Nullable java.net.InetAddress);
+    method @NonNull public android.net.StaticIpConfiguration.Builder setIpAddress(@Nullable android.net.LinkAddress);
+  }
+
   public final class TestNetworkInterface implements android.os.Parcelable {
     ctor public TestNetworkInterface(android.os.ParcelFileDescriptor, String);
     method public int describeContents();
@@ -1371,37 +1382,6 @@
 
 }
 
-package android.net.captiveportal {
-
-  public final class CaptivePortalProbeResult {
-    ctor public CaptivePortalProbeResult(int);
-    ctor public CaptivePortalProbeResult(int, @Nullable String, @Nullable String);
-    ctor public CaptivePortalProbeResult(int, @Nullable String, @Nullable String, @Nullable android.net.captiveportal.CaptivePortalProbeSpec);
-    method public boolean isFailed();
-    method public boolean isPartialConnectivity();
-    method public boolean isPortal();
-    method public boolean isSuccessful();
-    field @NonNull public static final android.net.captiveportal.CaptivePortalProbeResult FAILED;
-    field public static final int FAILED_CODE = 599; // 0x257
-    field public static final android.net.captiveportal.CaptivePortalProbeResult PARTIAL;
-    field public static final int PORTAL_CODE = 302; // 0x12e
-    field @NonNull public static final android.net.captiveportal.CaptivePortalProbeResult SUCCESS;
-    field public static final int SUCCESS_CODE = 204; // 0xcc
-    field @Nullable public final String detectUrl;
-    field @Nullable public final android.net.captiveportal.CaptivePortalProbeSpec probeSpec;
-    field @Nullable public final String redirectUrl;
-  }
-
-  public abstract class CaptivePortalProbeSpec {
-    method @NonNull public String getEncodedSpec();
-    method @NonNull public abstract android.net.captiveportal.CaptivePortalProbeResult getResult(int, @Nullable String);
-    method @NonNull public java.net.URL getUrl();
-    method @NonNull public static java.util.Collection<android.net.captiveportal.CaptivePortalProbeSpec> parseCaptivePortalProbeSpecs(@NonNull String);
-    method @Nullable public static android.net.captiveportal.CaptivePortalProbeSpec parseSpecOrNull(@Nullable String);
-  }
-
-}
-
 package android.net.metrics {
 
   public final class ApfProgramEvent implements android.net.metrics.IpConnectivityLog.Event {
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 98ab666..680ccfc 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -112,6 +112,11 @@
             return;
         }
 
+        if ("activated".equals(op)) {
+            doActivated(userId);
+            return;
+        }
+
         if (!isBackupActive(userId)) {
             return;
         }
@@ -200,6 +205,21 @@
         return true;
     }
 
+    private String activatedToString(boolean activated) {
+        return activated ? "activated" : "deactivated";
+    }
+
+    private void doActivated(@UserIdInt int userId) {
+        try {
+            System.out.println("Backup Manager currently "
+                    + activatedToString(mBmgr.isBackupServiceActive(userId)));
+        } catch (RemoteException e) {
+            System.err.println(e.toString());
+            System.err.println(BMGR_NOT_RUNNING_ERR);
+        }
+
+    }
+
     private String enableToString(boolean enabled) {
         return enabled ? "enabled" : "disabled";
     }
@@ -907,6 +927,7 @@
         System.err.println("       bmgr cancel backups");
         System.err.println("       bmgr init TRANSPORT...");
         System.err.println("       bmgr activate BOOL");
+        System.err.println("       bmgr activated");
         System.err.println("");
         System.err.println("The '--user' option specifies the user on which the operation is run.");
         System.err.println("It must be the first argument before the operation.");
@@ -978,6 +999,9 @@
         System.err.println("If the argument is 'true' it will be activated, otherwise it will be");
         System.err.println("deactivated. When deactivated, the service will not be running and no");
         System.err.println("operations can be performed until activation.");
+        System.err.println("");
+        System.err.println("The 'activated' command reports the current activated/deactivated");
+        System.err.println("state of the backup mechanism.");
     }
 
     private static class BackupMonitor extends IBackupManagerMonitor.Stub {
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 6d5fe7b..49470b4 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -269,12 +269,10 @@
 std::string ConcatPolicies(const std::vector<std::string>& policies) {
   std::string message;
   for (const std::string& policy : policies) {
-    if (message.empty()) {
-      message.append(policy);
-    } else {
-      message.append(policy);
+    if (!message.empty()) {
       message.append("|");
     }
+    message.append(policy);
   }
 
   return message;
diff --git a/cmds/incidentd/src/Broadcaster.cpp b/cmds/incidentd/src/Broadcaster.cpp
index 39e5393..63464f2 100644
--- a/cmds/incidentd/src/Broadcaster.cpp
+++ b/cmds/incidentd/src/Broadcaster.cpp
@@ -22,6 +22,7 @@
 
 #include <android/os/DropBoxManager.h>
 #include <binder/IServiceManager.h>
+#include <thread>
 
 namespace android {
 namespace os {
@@ -391,13 +392,20 @@
         return NO_ERROR;
     }
 
-    // Start a thread to write the data to dropbox.
-    int readFd = -1;
-    err = file->startFilteringData(&readFd, args);
-    if (err != NO_ERROR) {
-        return err;
+    int fds[2];
+    if (pipe(fds) != 0) {
+        ALOGW("Error opening pipe to filter incident report: %s", file->getDataFileName().c_str());
+        return NO_ERROR;
     }
 
+    int readFd = fds[0];
+    int writeFd = fds[1];
+
+    // spawn a thread to write the data. Release the writeFd ownership to the thread.
+    thread th([file, writeFd, args]() { file->startFilteringData(writeFd, args); });
+
+    th.detach();
+
     // Takes ownership of readFd.
     Status status = dropbox->addFile(String16("incident"), readFd, 0);
     if (!status.isOk()) {
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 4ba31b4..b4021d1b 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -32,6 +32,7 @@
 #include <log/log.h>
 #include <private/android_filesystem_config.h>
 #include <utils/Looper.h>
+#include <thread>
 
 #include <unistd.h>
 
@@ -117,7 +118,8 @@
 }
 
 static string build_uri(const string& pkg, const string& cls, const string& id) {
-    return "build_uri not implemented " + pkg + "/" + cls + "/" + id;
+    return "content://android.os.IncidentManager/pending?pkg="
+        + pkg + "&receiver=" + cls + "&r=" + id;
 }
 
 // ================================================================================
@@ -358,17 +360,21 @@
     IncidentReportArgs args;
     sp<ReportFile> file = mWorkDirectory->getReport(pkg, cls, id, &args);
     if (file != nullptr) {
-        int fd;
-        err = file->startFilteringData(&fd, args);
-        if (err != 0) {
-            ALOGW("Error reading data file that we think should exist: %s",
-                    file->getDataFileName().c_str());
+        // Create pipe
+        int fds[2];
+        if (pipe(fds) != 0) {
+            ALOGW("Error opening pipe to filter incident report: %s",
+                  file->getDataFileName().c_str());
             return Status::ok();
         }
-
         result->setTimestampNs(file->getTimestampNs());
         result->setPrivacyPolicy(file->getEnvelope().privacy_policy());
-        result->takeFileDescriptor(fd);
+        result->takeFileDescriptor(fds[0]);
+        int writeFd = fds[1];
+        // spawn a thread to write the data. Release the writeFd ownership to the thread.
+        thread th([file, writeFd, args]() { file->startFilteringData(writeFd, args); });
+
+        th.detach();
     }
 
     return Status::ok();
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index e773e74..218c1b2 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -551,7 +551,7 @@
              buf++) {
             // If there was an error now, there will be an error later and we will remove
             // it from the list then.
-            write_header_section(request->getFd(), *buf);
+            write_header_section(request->getFd(), buf->data(), buf->size());
         }
     });
 
diff --git a/cmds/incidentd/src/WorkDirectory.cpp b/cmds/incidentd/src/WorkDirectory.cpp
index aa376dd..ae640c6 100644
--- a/cmds/incidentd/src/WorkDirectory.cpp
+++ b/cmds/incidentd/src/WorkDirectory.cpp
@@ -18,6 +18,7 @@
 
 #include "WorkDirectory.h"
 
+#include "proto_util.h"
 #include "PrivacyFilter.h"
 
 #include <google/protobuf/io/zero_copy_stream_impl.h>
@@ -64,6 +65,9 @@
  */
 const ComponentName DROPBOX_SENTINEL("android", "DROPBOX");
 
+/** metadata field id in IncidentProto */
+const int FIELD_ID_INCIDENT_METADATA = 2;
+
 /**
  * Read a protobuf from disk into the message.
  */
@@ -386,65 +390,53 @@
     }
 }
 
-status_t ReportFile::startFilteringData(int* fd, const IncidentReportArgs& args) {
+status_t ReportFile::startFilteringData(int writeFd, const IncidentReportArgs& args) {
     // Open data file.
     int dataFd = open(mDataFileName.c_str(), O_RDONLY | O_CLOEXEC);
     if (dataFd < 0) {
+        ALOGW("Error opening incident report '%s' %s", getDataFileName().c_str(), strerror(-errno));
+        close(writeFd);
         return -errno;
     }
 
     // Check that the size on disk is what we thought we wrote.
     struct stat st;
     if (fstat(dataFd, &st) != 0) {
+        ALOGW("Error running fstat incident report '%s' %s", getDataFileName().c_str(),
+              strerror(-errno));
+        close(writeFd);
         return -errno;
     }
     if (st.st_size != mEnvelope.data_file_size()) {
         ALOGW("File size mismatch. Envelope says %" PRIi64 " bytes but data file is %" PRIi64
-                " bytes: %s", (int64_t)mEnvelope.data_file_size(), st.st_size,
-                mDataFileName.c_str());
+              " bytes: %s",
+              (int64_t)mEnvelope.data_file_size(), st.st_size, mDataFileName.c_str());
         ALOGW("Removing incident report");
         mWorkDirectory->remove(this);
+        close(writeFd);
         return BAD_VALUE;
     }
 
-    // Create pipe
-    int fds[2];
-    if (pipe(fds) != 0) {
-        ALOGW("Error opening pipe to filter incident report: %s", getDataFileName().c_str());
-        return -errno;
+    status_t err;
+
+    for (const auto& report : mEnvelope.report()) {
+        for (const auto& header : report.header()) {
+           write_header_section(writeFd,
+               reinterpret_cast<const uint8_t*>(header.c_str()), header.size());
+        }
     }
 
-    *fd = fds[0];
-    int writeFd = fds[1];
+    if (mEnvelope.has_metadata()) {
+        write_section(writeFd, FIELD_ID_INCIDENT_METADATA, mEnvelope.metadata());
+    }
 
-    // Spawn off a thread to do the filtering and writing
-    thread th([this, dataFd, writeFd, args]() {
-        ALOGD("worker thread started dataFd=%d writeFd=%d", dataFd, writeFd);
-        status_t err;
+    err = filter_and_write_report(writeFd, dataFd, mEnvelope.privacy_policy(), args);
+    if (err != NO_ERROR) {
+        ALOGW("Error writing incident report '%s' to dropbox: %s", getDataFileName().c_str(),
+                strerror(-err));
+    }
 
-        err = filter_and_write_report(writeFd, dataFd, mEnvelope.privacy_policy(), args);
-        close(writeFd);
-
-        if (err != NO_ERROR) {
-            ALOGW("Error writing incident report '%s' to dropbox: %s", getDataFileName().c_str(),
-                    strerror(-err));
-            // If there's an error here, there will also be an error returned from
-            // addFile, so we'll use that error to reschedule the send_to_dropbox.
-            // If the file is corrupted, we will put some logs in logcat, but won't
-            // actually return an error.
-            return;
-        }
-    });
-
-    // Better would be to join this thread after write is back, but there is no
-    // timeout parameter for that, which means we can't clean up if system server
-    // is stuck. Better is to leak the thread, which will eventually clean itself
-    // up after system server eventually dies, which it probably will.
-    th.detach();
-
-    // If the thread fails to start, we should return an error, but the thread
-    // class doesn't give us a good way to determine that.  Just pretend everything
-    // is ok.
+    close(writeFd);
     return NO_ERROR;
 }
 
@@ -501,7 +493,7 @@
 
 
 // ================================================================================
-// 
+//
 
 WorkDirectory::WorkDirectory()
         :mDirectory("/data/misc/incidents"),
diff --git a/cmds/incidentd/src/WorkDirectory.h b/cmds/incidentd/src/WorkDirectory.h
index e344371..3c6a2f2 100644
--- a/cmds/incidentd/src/WorkDirectory.h
+++ b/cmds/incidentd/src/WorkDirectory.h
@@ -135,14 +135,14 @@
     void closeDataFile();
 
     /**
-     * Spawn a thread to start writing and filtering data to a pipe, the read end of which
-     * will be returned in fd.  This thread will be detached and run until somebody finishes
-     * reading from the fd or closes it.  If there is an error, returns it and you will not
-     * get an fd.
+     * Use the privacy and section configuration from the args parameter to filter data, write
+     * to [writeFd] and take the ownership of [writeFd].
      *
-     * Use the privacy and section configuraiton from the args parameter.
+     * Note: this call is blocking. When the writeFd is a pipe fd for IPC, caller should make sure
+     * it's called on a separate thread so that reader can start to read without waiting for writer
+     * to finish writing (which may not happen due to pipe buffer overflow).
      */
-    status_t startFilteringData(int* fd, const IncidentReportArgs& args);
+    status_t startFilteringData(int writeFd, const IncidentReportArgs& args);
 
     /**
      * Get the name of the data file on disk.
diff --git a/cmds/incidentd/src/proto_util.cpp b/cmds/incidentd/src/proto_util.cpp
index be2f24f..4e8ff71 100644
--- a/cmds/incidentd/src/proto_util.cpp
+++ b/cmds/incidentd/src/proto_util.cpp
@@ -33,11 +33,10 @@
 // special section ids
 const int FIELD_ID_INCIDENT_HEADER = 1;
 
-status_t write_header_section(int fd, const vector<uint8_t>& buf) {
+status_t write_header_section(int fd, const uint8_t* buf, size_t bufSize) {
     status_t err;
-    const size_t bufSize = buf.size();
 
-    if (buf.empty()) {
+    if (bufSize == 0) {
         return NO_ERROR;
     }
 
@@ -46,7 +45,7 @@
         return err;
     }
 
-    err = WriteFully(fd, (uint8_t const*)buf.data(), bufSize);
+    err = WriteFully(fd, buf, bufSize);
     if (err != NO_ERROR) {
         return err;
     }
diff --git a/cmds/incidentd/src/proto_util.h b/cmds/incidentd/src/proto_util.h
index b9df6cb..2c0ab48 100644
--- a/cmds/incidentd/src/proto_util.h
+++ b/cmds/incidentd/src/proto_util.h
@@ -32,7 +32,7 @@
 /**
  * Write the IncidentHeaderProto section
  */
-status_t write_header_section(int fd, const vector<uint8_t>& buf);
+status_t write_header_section(int fd, const uint8_t* buf, size_t bufSize);
 
 /**
  * Write the prologue for a section in the incident report
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 8de1881..305a4ce 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -66,7 +66,7 @@
 
     void onDumpReport(const ConfigKey& key, const int64_t dumpTimeNs,
                       const bool include_current_partial_bucket, const bool erase_data,
-                      const DumpReportReason dumpReportReason, 
+                      const DumpReportReason dumpReportReason,
                       const DumpLatency dumpLatency,
                       vector<uint8_t>* outData);
     void onDumpReport(const ConfigKey& key, const int64_t dumpTimeNs,
@@ -260,6 +260,9 @@
     FRIEND_TEST(AlarmE2eTest, TestMultipleAlarms);
     FRIEND_TEST(ConfigTtlE2eTest, TestCountMetric);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetric);
+    FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation);
+    FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations);
+    FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index cadc3a08..cbb78bf 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -259,6 +259,7 @@
         BiometricEnrolled biometric_enrolled = 184;
         SystemServerWatchdogOccurred system_server_watchdog_occurred = 185;
         TombStoneOccurred tomb_stone_occurred = 186;
+        BluetoothClassOfDeviceReported bluetooth_class_of_device_reported = 187;
     }
 
     // Pulled events will start at field 10000.
@@ -1405,7 +1406,7 @@
     // Currently is last two bytes of a hash of a device level ID and
     // the mac address of the bluetooth device that is connected.
     // Deprecated: use obfuscated_id instead, this one is always 0 for Q+
-    optional int32 OBSOLETE_obfuscated_id = 2 [deprecated = true];
+    optional int32 obfuscated_id = 2 [deprecated = true];
     // The profile that is connected. Eg. GATT, A2DP, HEADSET.
     // From android.bluetooth.BluetoothAdapter.java
     // Default: 0 when not used
@@ -1416,7 +1417,7 @@
     // Hash algorithm: HMAC-SHA256
     // Size: 32 byte
     // Default: null or empty if the device identifier is not known
-    optional bytes obfuscated_id = 4 [(android.os.statsd.log_mode) = MODE_BYTES];
+    optional bytes new_obfuscated_id = 4 [(android.os.statsd.log_mode) = MODE_BYTES];
 }
 
 /**
@@ -2142,6 +2143,29 @@
 }
 
 /**
+ * Logs when Class of Device (CoD) value is learnt for a device during pairing or connection
+ *
+ * Logged from:
+ *   packages/apps/Bluetooth/src/com/android/bluetooth/btservice/BondStateMachine.java
+ *   packages/apps/Bluetooth/src/com/android/bluetooth/btservice/RemoteDevices.java
+ *
+ */
+message BluetoothClassOfDeviceReported {
+    // An identifier that can be used to match events for this device.
+    // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+    // Salt: Randomly generated 256 bit value
+    // Hash algorithm: HMAC-SHA256
+    // Size: 32 byte
+    // Default: null or empty if this is a server listener socket
+    optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+    // Class of Device (CoD) value including both Major, Minor device class and service class
+    // Defined in: https://www.bluetooth.com/specifications/assigned-numbers/baseband
+    // Also defined in: https://developer.android.com/reference/android/bluetooth/BluetoothClass
+    // Default: 0
+    optional int32 class_of_device = 2;
+}
+
+/**
  * Logs when something is plugged into or removed from the USB-C connector.
  *
  * Logged from:
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 4cf5333..5ed95ed 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -70,11 +70,11 @@
 bool MetricProducer::evaluateActiveStateLocked(int64_t elapsedTimestampNs) {
     bool isActive = mEventActivationMap.empty();
     for (auto& it : mEventActivationMap) {
-        if (it.second.state == ActivationState::kActive &&
-            elapsedTimestampNs > it.second.ttl_ns + it.second.activation_ns) {
-            it.second.state = ActivationState::kNotActive;
+        if (it.second->state == ActivationState::kActive &&
+            elapsedTimestampNs > it.second->ttl_ns + it.second->activation_ns) {
+            it.second->state = ActivationState::kNotActive;
         }
-        if (it.second.state == ActivationState::kActive) {
+        if (it.second->state == ActivationState::kActive) {
             isActive = true;
         }
     }
@@ -92,7 +92,8 @@
     }
 }
 
-void MetricProducer::addActivation(int activationTrackerIndex, int64_t ttl_seconds) {
+void MetricProducer::addActivation(int activationTrackerIndex, int64_t ttl_seconds,
+                                   int deactivationTrackerIndex) {
     std::lock_guard<std::mutex> lock(mMutex);
     // When a metric producer does not depend on any activation, its mIsActive is true.
     // Therefore, if this is the 1st activation, mIsActive will turn to false. Otherwise it does not
@@ -100,7 +101,12 @@
     if  (mEventActivationMap.empty()) {
         mIsActive = false;
     }
-    mEventActivationMap[activationTrackerIndex].ttl_ns = ttl_seconds * NS_PER_SEC;
+    std::shared_ptr<Activation> activation = std::make_shared<Activation>();
+    activation->ttl_ns = ttl_seconds * NS_PER_SEC;
+    mEventActivationMap.emplace(activationTrackerIndex, activation);
+    if (-1 != deactivationTrackerIndex) {
+        mEventDeactivationMap.emplace(deactivationTrackerIndex, activation);
+    }
 }
 
 void MetricProducer::activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs) {
@@ -109,27 +115,35 @@
         return;
     }
     if (mActivationType == MetricActivation::ACTIVATE_ON_BOOT &&
-        it->second.state == ActivationState::kNotActive) {
-        it->second.state = ActivationState::kActiveOnBoot;
+        it->second->state == ActivationState::kNotActive) {
+        it->second->state = ActivationState::kActiveOnBoot;
         return;
     }
-    it->second.activation_ns = elapsedTimestampNs;
-    it->second.state = ActivationState::kActive;
+    it->second->activation_ns = elapsedTimestampNs;
+    it->second->state = ActivationState::kActive;
     mIsActive = true;
 }
 
+void MetricProducer::cancelEventActivationLocked(int deactivationTrackerIndex) {
+    auto it = mEventDeactivationMap.find(deactivationTrackerIndex);
+    if (it == mEventDeactivationMap.end()) {
+        return;
+    }
+    it->second->state = ActivationState::kNotActive;
+}
+
 void MetricProducer::setActiveLocked(int64_t currentTimeNs, int64_t remainingTtlNs) {
     if (mEventActivationMap.size() == 0) {
         return;
     }
     for (auto& pair : mEventActivationMap) {
         auto& activation = pair.second;
-        if (activation.ttl_ns >= remainingTtlNs) {
-            activation.activation_ns = currentTimeNs + remainingTtlNs - activation.ttl_ns;
-            activation.state = kActive;
+        if (activation->ttl_ns >= remainingTtlNs) {
+            activation->activation_ns = currentTimeNs + remainingTtlNs - activation->ttl_ns;
+            activation->state = kActive;
             mIsActive = true;
-            VLOG("setting new activation time to %lld, %lld, %lld",
-                 (long long)activation.activation_ns, (long long)currentTimeNs,
+            VLOG("setting new activation->time to %lld, %lld, %lld",
+                 (long long)activation->activation_ns, (long long)currentTimeNs,
                  (long long)remainingTtlNs);
             return;
         }
@@ -140,8 +154,8 @@
 int64_t MetricProducer::getRemainingTtlNsLocked(int64_t currentTimeNs) const {
     int64_t maxTtl = 0;
     for (const auto& activation : mEventActivationMap) {
-        if (activation.second.state == kActive) {
-            maxTtl = std::max(maxTtl, activation.second.ttl_ns + activation.second.activation_ns -
+        if (activation.second->state == kActive) {
+            maxTtl = std::max(maxTtl, activation.second->ttl_ns + activation.second->activation_ns -
                                               currentTimeNs);
         }
     }
@@ -153,9 +167,9 @@
         return;
     }
     for (auto& activation : mEventActivationMap) {
-        if (activation.second.state == kActiveOnBoot) {
-            activation.second.state = kActive;
-            activation.second.activation_ns = currentTimeNs;
+        if (activation.second->state == kActiveOnBoot) {
+            activation.second->state = kActive;
+            activation.second->activation_ns = currentTimeNs;
             mIsActive = true;
         }
     }
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 046f996..70fbd47 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -228,6 +228,11 @@
         activateLocked(activationTrackerIndex, elapsedTimestampNs);
     }
 
+    void cancelEventActivation(int deactivationTrackerIndex) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        cancelEventActivationLocked(deactivationTrackerIndex);
+    }
+
     bool isActive() const {
         std::lock_guard<std::mutex> lock(mMutex);
         return isActiveLocked();
@@ -238,7 +243,8 @@
         prepActiveForBootIfNecessaryLocked(currentTimeNs);
     }
 
-    void addActivation(int activationTrackerIndex, int64_t ttl_seconds);
+    void addActivation(int activationTrackerIndex, int64_t ttl_seconds,
+                       int deactivationTrackerIndex = -1);
 
     inline void setActivationType(const MetricActivation::ActivationType& activationType) {
         mActivationType = activationType;
@@ -263,6 +269,7 @@
     bool evaluateActiveStateLocked(int64_t elapsedTimestampNs);
 
     void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs);
+    void cancelEventActivationLocked(int deactivationTrackerIndex);
 
     inline bool isActiveLocked() const {
         return mIsActive;
@@ -391,13 +398,19 @@
     };
     // When the metric producer has multiple activations, these activations are ORed to determine
     // whether the metric producer is ready to generate metrics.
-    std::unordered_map<int, Activation> mEventActivationMap;
+    std::unordered_map<int, std::shared_ptr<Activation>> mEventActivationMap;
+
+    // Maps index of atom matcher for deactivation to Activation struct.
+    std::unordered_map<int, std::shared_ptr<Activation>> mEventDeactivationMap;
 
     bool mIsActive;
 
     MetricActivation::ActivationType mActivationType;
 
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetric);
+    FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation);
+    FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations);
+    FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
 
     FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot);
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 4b3bfd3..095f9dd 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -74,7 +74,8 @@
             timeBaseNs, currentTimeNs, mTagIds, mAllAtomMatchers, mAllConditionTrackers,
             mAllMetricProducers, mAllAnomalyTrackers, mAllPeriodicAlarmTrackers,
             mConditionToMetricMap, mTrackerToMetricMap, mTrackerToConditionMap,
-            mActivationAtomTrackerToMetricMap, mMetricIndexesWithActivation, mNoReportMetricIds);
+            mActivationAtomTrackerToMetricMap, mDeactivationAtomTrackerToMetricMap,
+            mMetricIndexesWithActivation, mNoReportMetricIds);
 
     mHashStringsInReport = config.hash_strings_in_metric_report();
     mVersionStringsInReport = config.version_strings_in_metric_report();
@@ -255,7 +256,7 @@
 
 bool MetricsManager::checkLogCredentials(const LogEvent& event) {
     if (android::util::AtomsInfo::kWhitelistedAtoms.find(event.GetTagId()) !=
-      android::util::AtomsInfo::kWhitelistedAtoms.end()) 
+      android::util::AtomsInfo::kWhitelistedAtoms.end())
     {
         return true;
     }
@@ -344,24 +345,61 @@
     int64_t eventTimeNs = event.GetElapsedTimestampNs();
 
     bool isActive = mIsAlwaysActive;
-    for (int metric : mMetricIndexesWithActivation) {
-        mAllMetricProducers[metric]->flushIfExpire(eventTimeNs);
-        isActive |= mAllMetricProducers[metric]->isActive();
+
+    // Set of metrics that are still active after flushing.
+    unordered_set<int> activeMetricsIndices;
+
+    // Update state of all metrics w/ activation conditions as of eventTimeNs.
+    for (int metricIndex : mMetricIndexesWithActivation) {
+        const sp<MetricProducer>& metric = mAllMetricProducers[metricIndex];
+        metric->flushIfExpire(eventTimeNs);
+        if (metric->isActive()) {
+            // If this metric w/ activation condition is still active after
+            // flushing, remember it.
+            activeMetricsIndices.insert(metricIndex);
+        }
     }
 
-    mIsActive = isActive;
+    mIsActive = isActive || !activeMetricsIndices.empty();
 
     if (mTagIds.find(tagId) == mTagIds.end()) {
-        // not interesting...
+        // Not interesting...
         return;
     }
 
     vector<MatchingState> matcherCache(mAllAtomMatchers.size(), MatchingState::kNotComputed);
 
+    // Evaluate all atom matchers.
     for (auto& matcher : mAllAtomMatchers) {
         matcher->onLogEvent(event, mAllAtomMatchers, matcherCache);
     }
 
+    // Set of metrics that received an activation cancellation.
+    unordered_set<int> metricIndicesWithCanceledActivations;
+
+    // Determine which metric activations received a cancellation and cancel them.
+    for (const auto& it : mDeactivationAtomTrackerToMetricMap) {
+        if (matcherCache[it.first] == MatchingState::kMatched) {
+            for (int metricIndex : it.second) {
+                mAllMetricProducers[metricIndex]->cancelEventActivation(it.first);
+                metricIndicesWithCanceledActivations.insert(metricIndex);
+            }
+        }
+    }
+
+    // Determine whether any metrics are no longer active after cancelling metric activations.
+    for (const int metricIndex : metricIndicesWithCanceledActivations) {
+        const sp<MetricProducer>& metric = mAllMetricProducers[metricIndex];
+        metric->flushIfExpire(eventTimeNs);
+        if (!metric->isActive()) {
+            activeMetricsIndices.erase(metricIndex);
+        }
+    }
+
+    isActive |= !activeMetricsIndices.empty();
+
+
+    // Determine which metric activations should be turned on and turn them on
     for (const auto& it : mActivationAtomTrackerToMetricMap) {
         if (matcherCache[it.first] == MatchingState::kMatched) {
             for (int metricIndex : it.second) {
@@ -406,12 +444,12 @@
         if (pair != mConditionToMetricMap.end()) {
             auto& metricList = pair->second;
             for (auto metricIndex : metricList) {
-                // metric cares about non sliced condition, and it's changed.
+                // Metric cares about non sliced condition, and it's changed.
                 // Push the new condition to it directly.
                 if (!mAllMetricProducers[metricIndex]->isConditionSliced()) {
                     mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i],
                                                                          eventTimeNs);
-                    // metric cares about sliced conditions, and it may have changed. Send
+                    // Metric cares about sliced conditions, and it may have changed. Send
                     // notification, and the metric can query the sliced conditions that are
                     // interesting to it.
                 } else {
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 3904460..d317f8e 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -225,18 +225,21 @@
 
     // The following map is initialized from the statsd_config.
 
-    // maps from the index of the LogMatchingTracker to index of MetricProducer.
+    // Maps from the index of the LogMatchingTracker to index of MetricProducer.
     std::unordered_map<int, std::vector<int>> mTrackerToMetricMap;
 
-    // maps from LogMatchingTracker to ConditionTracker
+    // Maps from LogMatchingTracker to ConditionTracker
     std::unordered_map<int, std::vector<int>> mTrackerToConditionMap;
 
-    // maps from ConditionTracker to MetricProducer
+    // Maps from ConditionTracker to MetricProducer
     std::unordered_map<int, std::vector<int>> mConditionToMetricMap;
 
-    // maps from life span triggering event to MetricProducers.
+    // Maps from life span triggering event to MetricProducers.
     std::unordered_map<int, std::vector<int>> mActivationAtomTrackerToMetricMap;
 
+    // Maps deactivation triggering event to MetricProducers.
+    std::unordered_map<int, std::vector<int>> mDeactivationAtomTrackerToMetricMap;
+
     std::vector<int> mMetricIndexesWithActivation;
 
     void initLogSourceWhiteList();
@@ -281,6 +284,9 @@
     FRIEND_TEST(AlarmE2eTest, TestMultipleAlarms);
     FRIEND_TEST(ConfigTtlE2eTest, TestCountMetric);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetric);
+    FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation);
+    FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations);
+    FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
 
     FRIEND_TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead);
     FRIEND_TEST(StatsLogProcessorTest, TestActivationOnBoot);
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 463b5a0..082382c 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -711,6 +711,7 @@
                            const unordered_map<int64_t, int> &metricProducerMap,
                            vector<sp<MetricProducer>>& allMetricProducers,
                            unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+                           unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
                            vector<int>& metricsWithActivation) {
     for (int i = 0; i < config.metric_activation_size(); ++i) {
         const MetricActivation& metric_activation = config.metric_activation(i);
@@ -725,8 +726,8 @@
             ALOGE("Invalid metric tracker index.");
             return false;
         }
-        allMetricProducers[metricTrackerIndex]->setActivationType(
-                metric_activation.activation_type());
+        const sp<MetricProducer>& metric = allMetricProducers[metricTrackerIndex];
+        metric->setActivationType(metric_activation.activation_type());
         metricsWithActivation.push_back(metricTrackerIndex);
         for (int j = 0; j < metric_activation.event_activation_size(); ++j) {
             const EventActivation& activation = metric_activation.event_activation(j);
@@ -738,8 +739,22 @@
             const int atomMatcherIndex = logTrackerIt->second;
             activationAtomTrackerToMetricMap[atomMatcherIndex].push_back(
                 metricTrackerIndex);
-            allMetricProducers[metricTrackerIndex]->addActivation(
-                atomMatcherIndex, activation.ttl_seconds());
+
+            if (activation.has_deactivation_atom_matcher_id()) {
+                auto deactivationAtomMatcherIt =
+                        logEventTrackerMap.find(activation.deactivation_atom_matcher_id());
+                if (deactivationAtomMatcherIt == logEventTrackerMap.end()) {
+                    ALOGE("Atom matcher not found for event deactivation.");
+                    return false;
+                }
+                const int deactivationMatcherIndex = deactivationAtomMatcherIt->second;
+                deactivationAtomTrackerToMetricMap[deactivationMatcherIndex]
+                        .push_back(metricTrackerIndex);
+                metric->addActivation(atomMatcherIndex, activation.ttl_seconds(),
+                                      deactivationMatcherIndex);
+            } else {
+                metric->addActivation(atomMatcherIndex, activation.ttl_seconds());
+            }
         }
     }
     return true;
@@ -759,6 +774,7 @@
                       unordered_map<int, std::vector<int>>& trackerToMetricMap,
                       unordered_map<int, std::vector<int>>& trackerToConditionMap,
                       unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+                      unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
                       vector<int>& metricsWithActivation,
                       std::set<int64_t>& noReportMetricIds) {
     unordered_map<int64_t, int> logTrackerMap;
@@ -795,7 +811,8 @@
         return false;
     }
     if (!initMetricActivations(key, config, currentTimeNs, logTrackerMap, metricProducerMap,
-            allMetricProducers, activationAtomTrackerToMetricMap, metricsWithActivation)) {
+            allMetricProducers, activationAtomTrackerToMetricMap,
+            deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
         ALOGE("initMetricActivations failed");
         return false;
     }
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 9ffceda..028231f 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -108,8 +108,9 @@
                       std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
                       std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
                       std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
-                      unordered_map<int, std::vector<int>>& lifeSpanEventTrackerToMetricMap,
-                      vector<int>& metricsWithLifeSpan,
+                      unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+                      unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+                      vector<int>& metricsWithActivation,
                       std::set<int64_t>& noReportMetricIds);
 
 bool isStateTracker(const SimplePredicate& simplePredicate, std::vector<Matcher>* primaryKeys);
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 0e91f52..257e65e 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -380,6 +380,7 @@
 message EventActivation {
   optional int64 atom_matcher_id = 1;
   optional int64 ttl_seconds = 2;
+  optional int64 deactivation_atom_matcher_id = 3;
 }
 
 message MetricActivation {
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index f8184d8..71adc57 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -282,8 +282,9 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> lifeSpanEventTrackerToMetricMap;
-    vector<int> metricsWithLifeSpan;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    vector<int> metricsWithActivation;
     std::set<int64_t> noReportMetricIds;
 
     EXPECT_TRUE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
@@ -291,8 +292,8 @@
                                  allAtomMatchers, allConditionTrackers, allMetricProducers,
                                  allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
                                  trackerToMetricMap, trackerToConditionMap,
-                                 lifeSpanEventTrackerToMetricMap, metricsWithLifeSpan,
-                                 noReportMetricIds));
+                                 activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                                 metricsWithActivation, noReportMetricIds));
     EXPECT_EQ(1u, allMetricProducers.size());
     EXPECT_EQ(1u, allAnomalyTrackers.size());
     EXPECT_EQ(1u, noReportMetricIds.size());
@@ -313,8 +314,9 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> lifeSpanEventTrackerToMetricMap;
-    vector<int> metricsWithLifeSpan;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    vector<int> metricsWithActivation;
     std::set<int64_t> noReportMetricIds;
 
     EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
@@ -322,8 +324,8 @@
                                   allAtomMatchers, allConditionTrackers, allMetricProducers,
                                   allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
                                   trackerToMetricMap, trackerToConditionMap,
-                                  lifeSpanEventTrackerToMetricMap, metricsWithLifeSpan,
-                                  noReportMetricIds));
+                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                                  metricsWithActivation, noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, TestCircleLogMatcherDependency) {
@@ -341,8 +343,9 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> lifeSpanEventTrackerToMetricMap;
-    vector<int> metricsWithLifeSpan;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    vector<int> metricsWithActivation;
     std::set<int64_t> noReportMetricIds;
 
     EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
@@ -350,8 +353,8 @@
                                   allAtomMatchers, allConditionTrackers, allMetricProducers,
                                   allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
                                   trackerToMetricMap, trackerToConditionMap,
-                                  lifeSpanEventTrackerToMetricMap, metricsWithLifeSpan,
-                                  noReportMetricIds));
+                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                                  metricsWithActivation, noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, TestMissingMatchers) {
@@ -369,16 +372,17 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> lifeSpanEventTrackerToMetricMap;
-    vector<int> metricsWithLifeSpan;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    vector<int> metricsWithActivation;
     std::set<int64_t> noReportMetricIds;
     EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
                                   periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
                                   allAtomMatchers, allConditionTrackers, allMetricProducers,
                                   allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
                                   trackerToMetricMap, trackerToConditionMap,
-                                  lifeSpanEventTrackerToMetricMap, metricsWithLifeSpan,
-                                  noReportMetricIds));
+                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                                  metricsWithActivation, noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, TestMissingPredicate) {
@@ -396,16 +400,17 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> lifeSpanEventTrackerToMetricMap;
-    vector<int> metricsWithLifeSpan;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    vector<int> metricsWithActivation;
     std::set<int64_t> noReportMetricIds;
     EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
                                   periodicAlarmMonitor, timeBaseSec, timeBaseSec, allTagIds,
                                   allAtomMatchers, allConditionTrackers, allMetricProducers,
                                   allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
                                   trackerToMetricMap, trackerToConditionMap,
-                                  lifeSpanEventTrackerToMetricMap, metricsWithLifeSpan,
-                                  noReportMetricIds));
+                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                                  metricsWithActivation, noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, TestCirclePredicateDependency) {
@@ -423,8 +428,9 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> lifeSpanEventTrackerToMetricMap;
-    vector<int> metricsWithLifeSpan;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    vector<int> metricsWithActivation;
     std::set<int64_t> noReportMetricIds;
 
     EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
@@ -432,8 +438,8 @@
                                   allAtomMatchers, allConditionTrackers, allMetricProducers,
                                   allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
                                   trackerToMetricMap, trackerToConditionMap,
-                                  lifeSpanEventTrackerToMetricMap, metricsWithLifeSpan,
-                                  noReportMetricIds));
+                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                                  metricsWithActivation, noReportMetricIds));
 }
 
 TEST(MetricsManagerTest, testAlertWithUnknownMetric) {
@@ -451,8 +457,9 @@
     unordered_map<int, std::vector<int>> conditionToMetricMap;
     unordered_map<int, std::vector<int>> trackerToMetricMap;
     unordered_map<int, std::vector<int>> trackerToConditionMap;
-    unordered_map<int, std::vector<int>> lifeSpanEventTrackerToMetricMap;
-    vector<int> metricsWithLifeSpan;
+    unordered_map<int, std::vector<int>> activationAtomTrackerToMetricMap;
+    unordered_map<int, std::vector<int>> deactivationAtomTrackerToMetricMap;
+    vector<int> metricsWithActivation;
     std::set<int64_t> noReportMetricIds;
 
     EXPECT_FALSE(initStatsdConfig(kConfigKey, config, uidMap, pullerManager, anomalyAlarmMonitor,
@@ -460,8 +467,8 @@
                                   allAtomMatchers, allConditionTrackers, allMetricProducers,
                                   allAnomalyTrackers, allAlarmTrackers, conditionToMetricMap,
                                   trackerToMetricMap, trackerToConditionMap,
-                                  lifeSpanEventTrackerToMetricMap, metricsWithLifeSpan,
-                                  noReportMetricIds));
+                                  activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap,
+                                  metricsWithActivation, noReportMetricIds));
 }
 
 #else
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 88aa180..91e282a 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -610,26 +610,26 @@
     // Assert that all 3 metrics with activation are inactive and that the ttls were properly set.
     EXPECT_FALSE(metricProducer1003->isActive());
     const auto& activation1003 = metricProducer1003->mEventActivationMap.begin()->second;
-    EXPECT_EQ(100 * NS_PER_SEC, activation1003.ttl_ns);
-    EXPECT_EQ(0, activation1003.activation_ns);
+    EXPECT_EQ(100 * NS_PER_SEC, activation1003->ttl_ns);
+    EXPECT_EQ(0, activation1003->activation_ns);
     EXPECT_FALSE(metricProducer1005->isActive());
     const auto& activation1005 = metricProducer1005->mEventActivationMap.begin()->second;
-    EXPECT_EQ(100 * NS_PER_SEC, activation1005.ttl_ns);
-    EXPECT_EQ(0, activation1005.activation_ns);
+    EXPECT_EQ(100 * NS_PER_SEC, activation1005->ttl_ns);
+    EXPECT_EQ(0, activation1005->activation_ns);
     EXPECT_FALSE(metricProducer1006->isActive());
     const auto& activation1006 = metricProducer1006->mEventActivationMap.begin()->second;
-    EXPECT_EQ(200 * NS_PER_SEC, activation1006.ttl_ns);
-    EXPECT_EQ(0, activation1006.activation_ns);
+    EXPECT_EQ(200 * NS_PER_SEC, activation1006->ttl_ns);
+    EXPECT_EQ(0, activation1006->activation_ns);
 
     processor2->LoadMetricsActivationFromDisk();
 
     // After loading activations from disk, assert that all 3 metrics are active.
     EXPECT_TRUE(metricProducer1003->isActive());
-    EXPECT_EQ(timeBase2 + ttl3 - activation1003.ttl_ns, activation1003.activation_ns);
+    EXPECT_EQ(timeBase2 + ttl3 - activation1003->ttl_ns, activation1003->activation_ns);
     EXPECT_TRUE(metricProducer1005->isActive());
-    EXPECT_EQ(timeBase2 + ttl5 - activation1005.ttl_ns, activation1005.activation_ns);
+    EXPECT_EQ(timeBase2 + ttl5 - activation1005->ttl_ns, activation1005->activation_ns);
     EXPECT_TRUE(metricProducer1006->isActive());
-    EXPECT_EQ(timeBase2 + ttl6 - activation1006.ttl_ns, activation1003.activation_ns);
+    EXPECT_EQ(timeBase2 + ttl6 - activation1006->ttl_ns, activation1003->activation_ns);
 
     // Make sure no more broadcasts have happened.
     EXPECT_EQ(broadcastCount, 1);
@@ -696,17 +696,17 @@
     EXPECT_TRUE(metricProducer2->isActive());
 
     const auto& activation1 = metricProducer1->mEventActivationMap.begin()->second;
-    EXPECT_EQ(100 * NS_PER_SEC, activation1.ttl_ns);
-    EXPECT_EQ(0, activation1.activation_ns);
-    EXPECT_EQ(kNotActive, activation1.state);
+    EXPECT_EQ(100 * NS_PER_SEC, activation1->ttl_ns);
+    EXPECT_EQ(0, activation1->activation_ns);
+    EXPECT_EQ(kNotActive, activation1->state);
 
     std::vector<AttributionNodeInternal> attributions1 = {CreateAttribution(111, "App1")};
     auto event = CreateAcquireWakelockEvent(attributions1, "wl1", 100 + timeBase1);
     processor->OnLogEvent(event.get());
 
     EXPECT_FALSE(metricProducer1->isActive());
-    EXPECT_EQ(0, activation1.activation_ns);
-    EXPECT_EQ(kActiveOnBoot, activation1.state);
+    EXPECT_EQ(0, activation1->activation_ns);
+    EXPECT_EQ(kActiveOnBoot, activation1->state);
 
     int64_t shutDownTime = timeBase1 + 100 * NS_PER_SEC;
 
@@ -746,14 +746,14 @@
     EXPECT_TRUE(metricProducer1002->isActive());
 
     const auto& activation1001 = metricProducer1001->mEventActivationMap.begin()->second;
-    EXPECT_EQ(100 * NS_PER_SEC, activation1001.ttl_ns);
-    EXPECT_EQ(0, activation1001.activation_ns);
-    EXPECT_EQ(kNotActive, activation1001.state);
+    EXPECT_EQ(100 * NS_PER_SEC, activation1001->ttl_ns);
+    EXPECT_EQ(0, activation1001->activation_ns);
+    EXPECT_EQ(kNotActive, activation1001->state);
 
     processor2->LoadMetricsActivationFromDisk();
 
     EXPECT_TRUE(metricProducer1001->isActive());
-    EXPECT_EQ(timeBase2 + ttl1 - activation1001.ttl_ns, activation1001.activation_ns);
+    EXPECT_EQ(timeBase2 + ttl1 - activation1001->ttl_ns, activation1001->activation_ns);
 }
 
 #else
diff --git a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
index 7fb43f8a..bf52bb0 100644
--- a/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/MetricActivation_e2e_test.cpp
@@ -31,9 +31,9 @@
 StatsdConfig CreateStatsdConfig() {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+    auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher();
     auto crashMatcher = CreateProcessCrashAtomMatcher();
     auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
-    auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher();
 
     *config.add_atom_matcher() = saverModeMatcher;
     *config.add_atom_matcher() = crashMatcher;
@@ -60,13 +60,149 @@
     return config;
 }
 
+StatsdConfig CreateStatsdConfigWithOneDeactivation() {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+    auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher();
+    auto crashMatcher = CreateProcessCrashAtomMatcher();
+    auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
+    auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher();
+
+    *config.add_atom_matcher() = saverModeMatcher;
+    *config.add_atom_matcher() = crashMatcher;
+    *config.add_atom_matcher() = screenOnMatcher;
+    *config.add_atom_matcher() = brightnessChangedMatcher;
+
+    int64_t metricId = 123456;
+    auto countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(crashMatcher.id());
+    countMetric->set_bucket(FIVE_MINUTES);
+    countMetric->mutable_dimensions_in_what()->set_field(
+        android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    countMetric->mutable_dimensions_in_what()->add_child()->set_field(1);  // uid field
+
+    auto metric_activation1 = config.add_metric_activation();
+    metric_activation1->set_metric_id(metricId);
+    auto event_activation1 = metric_activation1->add_event_activation();
+    event_activation1->set_atom_matcher_id(saverModeMatcher.id());
+    event_activation1->set_ttl_seconds(60 * 6);  // 6 minutes
+    event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id());
+    auto event_activation2 = metric_activation1->add_event_activation();
+    event_activation2->set_atom_matcher_id(screenOnMatcher.id());
+    event_activation2->set_ttl_seconds(60 * 2);  // 2 minutes
+
+    return config;
+}
+
+StatsdConfig CreateStatsdConfigWithTwoDeactivations() {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+    auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher();
+    auto crashMatcher = CreateProcessCrashAtomMatcher();
+    auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
+    auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher();
+    auto brightnessChangedMatcher2 = CreateScreenBrightnessChangedAtomMatcher();
+    brightnessChangedMatcher2.set_id(StringToId("ScreenBrightnessChanged2"));
+
+    *config.add_atom_matcher() = saverModeMatcher;
+    *config.add_atom_matcher() = crashMatcher;
+    *config.add_atom_matcher() = screenOnMatcher;
+    *config.add_atom_matcher() = brightnessChangedMatcher;
+    *config.add_atom_matcher() = brightnessChangedMatcher2;
+
+    int64_t metricId = 123456;
+    auto countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(crashMatcher.id());
+    countMetric->set_bucket(FIVE_MINUTES);
+    countMetric->mutable_dimensions_in_what()->set_field(
+        android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    countMetric->mutable_dimensions_in_what()->add_child()->set_field(1);  // uid field
+
+    auto metric_activation1 = config.add_metric_activation();
+    metric_activation1->set_metric_id(metricId);
+    auto event_activation1 = metric_activation1->add_event_activation();
+    event_activation1->set_atom_matcher_id(saverModeMatcher.id());
+    event_activation1->set_ttl_seconds(60 * 6);  // 6 minutes
+    event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id());
+    auto event_activation2 = metric_activation1->add_event_activation();
+    event_activation2->set_atom_matcher_id(screenOnMatcher.id());
+    event_activation2->set_ttl_seconds(60 * 2);  // 2 minutes
+    event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher2.id());
+
+    return config;
+}
+
+StatsdConfig CreateStatsdConfigWithTwoMetricsTwoDeactivations() {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+    auto saverModeMatcher = CreateBatterySaverModeStartAtomMatcher();
+    auto crashMatcher = CreateProcessCrashAtomMatcher();
+    auto foregroundMatcher = CreateMoveToForegroundAtomMatcher();
+    auto screenOnMatcher = CreateScreenTurnedOnAtomMatcher();
+    auto brightnessChangedMatcher = CreateScreenBrightnessChangedAtomMatcher();
+    auto brightnessChangedMatcher2 = CreateScreenBrightnessChangedAtomMatcher();
+    brightnessChangedMatcher2.set_id(StringToId("ScreenBrightnessChanged2"));
+
+    *config.add_atom_matcher() = saverModeMatcher;
+    *config.add_atom_matcher() = crashMatcher;
+    *config.add_atom_matcher() = screenOnMatcher;
+    *config.add_atom_matcher() = brightnessChangedMatcher;
+    *config.add_atom_matcher() = brightnessChangedMatcher2;
+    *config.add_atom_matcher() = foregroundMatcher;
+
+    int64_t metricId = 123456;
+    auto countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(crashMatcher.id());
+    countMetric->set_bucket(FIVE_MINUTES);
+    countMetric->mutable_dimensions_in_what()->set_field(
+        android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED);
+    countMetric->mutable_dimensions_in_what()->add_child()->set_field(1);  // uid field
+
+    int64_t metricId2 = 234567;
+    countMetric = config.add_count_metric();
+    countMetric->set_id(metricId2);
+    countMetric->set_what(foregroundMatcher.id());
+    countMetric->set_bucket(FIVE_MINUTES);
+    countMetric->mutable_dimensions_in_what()->set_field(
+        android::util::ACTIVITY_FOREGROUND_STATE_CHANGED);
+    countMetric->mutable_dimensions_in_what()->add_child()->set_field(1);  // uid field
+
+    auto metric_activation1 = config.add_metric_activation();
+    metric_activation1->set_metric_id(metricId);
+    auto event_activation1 = metric_activation1->add_event_activation();
+    event_activation1->set_atom_matcher_id(saverModeMatcher.id());
+    event_activation1->set_ttl_seconds(60 * 6);  // 6 minutes
+    event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id());
+    auto event_activation2 = metric_activation1->add_event_activation();
+    event_activation2->set_atom_matcher_id(screenOnMatcher.id());
+    event_activation2->set_ttl_seconds(60 * 2);  // 2 minutes
+    event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher2.id());
+
+    metric_activation1 = config.add_metric_activation();
+    metric_activation1->set_metric_id(metricId2);
+    event_activation1 = metric_activation1->add_event_activation();
+    event_activation1->set_atom_matcher_id(saverModeMatcher.id());
+    event_activation1->set_ttl_seconds(60 * 6);  // 6 minutes
+    event_activation1->set_deactivation_atom_matcher_id(brightnessChangedMatcher.id());
+    event_activation2 = metric_activation1->add_event_activation();
+    event_activation2->set_atom_matcher_id(screenOnMatcher.id());
+    event_activation2->set_ttl_seconds(60 * 2);  // 2 minutes
+    event_activation2->set_deactivation_atom_matcher_id(brightnessChangedMatcher2.id());
+
+    return config;
+}
+
 }  // namespace
 
 TEST(MetricActivationE2eTest, TestCountMetric) {
     auto config = CreateStatsdConfig();
 
-    int64_t bucketStartTimeNs = 10000000000;
-    int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
+    int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs
+    int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
 
     int uid = 12345;
     int64_t cfgId = 98765;
@@ -108,12 +244,12 @@
     EXPECT_EQ(eventActivationMap.size(), 2u);
     EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end());
     EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
-    EXPECT_EQ(eventActivationMap[0].state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0].activation_ns, 0);
-    EXPECT_EQ(eventActivationMap[0].ttl_ns, 60 * 6 * NS_PER_SEC);
-    EXPECT_EQ(eventActivationMap[2].state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2].activation_ns, 0);
-    EXPECT_EQ(eventActivationMap[2].ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
 
     std::unique_ptr<LogEvent> event;
 
@@ -131,12 +267,12 @@
     EXPECT_EQ(broadcastCount, 1);
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
-    EXPECT_EQ(eventActivationMap[0].state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0].activation_ns, bucketStartTimeNs + 10);
-    EXPECT_EQ(eventActivationMap[0].ttl_ns, 60 * 6 * NS_PER_SEC);
-    EXPECT_EQ(eventActivationMap[2].state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2].activation_ns, 0);
-    EXPECT_EQ(eventActivationMap[2].ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
 
     // First processed event.
     event = CreateAppCrashEvent(222, bucketStartTimeNs + 15);
@@ -148,12 +284,12 @@
     processor.OnLogEvent(event.get());
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
-    EXPECT_EQ(eventActivationMap[0].state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0].activation_ns, bucketStartTimeNs + 10);
-    EXPECT_EQ(eventActivationMap[0].ttl_ns, 60 * 6 * NS_PER_SEC);
-    EXPECT_EQ(eventActivationMap[2].state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[2].activation_ns, bucketStartTimeNs + 20);
-    EXPECT_EQ(eventActivationMap[2].ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
 
     // 2nd processed event.
     // The activation by screen_on event expires, but the one by battery save mode is still active.
@@ -161,12 +297,12 @@
     processor.OnLogEvent(event.get());
     EXPECT_TRUE(metricsManager->isActive());
     EXPECT_TRUE(metricProducer->mIsActive);
-    EXPECT_EQ(eventActivationMap[0].state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[0].activation_ns, bucketStartTimeNs + 10);
-    EXPECT_EQ(eventActivationMap[0].ttl_ns, 60 * 6 * NS_PER_SEC);
-    EXPECT_EQ(eventActivationMap[2].state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2].activation_ns, bucketStartTimeNs + 20);
-    EXPECT_EQ(eventActivationMap[2].ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
     // No new broadcast since the config should still be active.
     EXPECT_EQ(broadcastCount, 1);
 
@@ -182,14 +318,14 @@
     // New broadcast since the config is no longer active.
     EXPECT_EQ(broadcastCount, 2);
     EXPECT_EQ(activeConfigsBroadcast.size(), 0);
-    EXPECT_EQ(eventActivationMap[0].state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0].activation_ns, bucketStartTimeNs + 10);
-    EXPECT_EQ(eventActivationMap[0].ttl_ns, 60 * 6 * NS_PER_SEC);
-    EXPECT_EQ(eventActivationMap[2].state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[2].activation_ns, bucketStartTimeNs + 20);
-    EXPECT_EQ(eventActivationMap[2].ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
 
-    // Re-activate.
+    // Re-activate metric via screen on.
     event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
                                           bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
     processor.OnLogEvent(event.get());
@@ -198,13 +334,14 @@
     EXPECT_EQ(broadcastCount, 3);
     EXPECT_EQ(activeConfigsBroadcast.size(), 1);
     EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
-    EXPECT_EQ(eventActivationMap[0].state, ActivationState::kNotActive);
-    EXPECT_EQ(eventActivationMap[0].activation_ns, bucketStartTimeNs + 10);
-    EXPECT_EQ(eventActivationMap[0].ttl_ns, 60 * 6 * NS_PER_SEC);
-    EXPECT_EQ(eventActivationMap[2].state, ActivationState::kActive);
-    EXPECT_EQ(eventActivationMap[2].activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
-    EXPECT_EQ(eventActivationMap[2].ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
 
+    // 4th processed event.
     event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
     processor.OnLogEvent(event.get());
 
@@ -272,9 +409,1192 @@
               data.bucket_info(0).start_bucket_elapsed_nanos());
     EXPECT_EQ(bucketStartTimeNs + 3 * bucketSizeNs,
               data.bucket_info(0).end_bucket_elapsed_nanos());
-
 }
 
+TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation) {
+    auto config = CreateStatsdConfigWithOneDeactivation();
+
+    int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs
+    int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
+
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    sp<UidMap> m = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> subscriberAlarmMonitor;
+    vector<int64_t> activeConfigsBroadcast;
+
+    long timeBase1 = 1;
+    int broadcastCount = 0;
+    StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor,
+            bucketStartTimeNs, [](const ConfigKey& key) { return true; },
+            [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid,
+                    const vector<int64_t>& activeConfigs) {
+                broadcastCount++;
+                EXPECT_EQ(broadcastUid, uid);
+                activeConfigsBroadcast.clear();
+                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(),
+                        activeConfigs.begin(), activeConfigs.end());
+                return true;
+            });
+
+    processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
+
+    EXPECT_EQ(processor.mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    auto& eventActivationMap = metricProducer->mEventActivationMap;
+    auto& eventDeactivationMap = metricProducer->mEventDeactivationMap;
+
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_FALSE(metricProducer->mIsActive);
+    // Two activations: one is triggered by battery saver mode (tracker index 0), the other is
+    // triggered by screen on event (tracker index 2).
+    EXPECT_EQ(eventActivationMap.size(), 2u);
+    EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end());
+    EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap.size(), 1u);
+    EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end());
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+
+    std::unique_ptr<LogEvent> event;
+
+    event = CreateAppCrashEvent(111, bucketStartTimeNs + 5);
+    processor.OnLogEvent(event.get());
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_FALSE(metricProducer->mIsActive);
+    EXPECT_EQ(broadcastCount, 0);
+
+    // Activated by battery save mode.
+    event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(broadcastCount, 1);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+
+    // First processed event.
+    event = CreateAppCrashEvent(222, bucketStartTimeNs + 15);
+    processor.OnLogEvent(event.get());
+
+    // Activated by screen on event.
+    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+                                          bucketStartTimeNs + 20);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+
+    // 2nd processed event.
+    // The activation by screen_on event expires, but the one by battery save mode is still active.
+    event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    // No new broadcast since the config should still be active.
+    EXPECT_EQ(broadcastCount, 1);
+
+    // 3rd processed event.
+    event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
+    processor.OnLogEvent(event.get());
+
+    // All activations expired.
+    event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8);
+    processor.OnLogEvent(event.get());
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_FALSE(metricProducer->mIsActive);
+    // New broadcast since the config is no longer active.
+    EXPECT_EQ(broadcastCount, 2);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+
+    // Re-activate metric via screen on.
+    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+                                          bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(broadcastCount, 3);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+
+    // 4th processed event.
+    event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
+    processor.OnLogEvent(event.get());
+
+    // Re-enable battery saver mode activation.
+    event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(broadcastCount, 3);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+
+    // 5th processed event.
+    event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
+    processor.OnLogEvent(event.get());
+
+    // Cancel battery saver mode activation.
+    event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(broadcastCount, 3);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+
+    // Screen-on activation expired.
+    event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13);
+    processor.OnLogEvent(event.get());
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_FALSE(metricProducer->mIsActive);
+    // New broadcast since the config is no longer active.
+    EXPECT_EQ(broadcastCount, 4);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+
+    event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
+    processor.OnLogEvent(event.get());
+
+    // Re-enable battery saver mode activation.
+    event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(broadcastCount, 5);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+
+    // Cancel battery saver mode activation.
+    event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16);
+    processor.OnLogEvent(event.get());
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_FALSE(metricProducer->mIsActive);
+    EXPECT_EQ(broadcastCount, 6);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true,
+                            ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStartEndTimestamp(&reports);
+    EXPECT_EQ(1, reports.reports_size());
+    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size());
+
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(
+            reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    EXPECT_EQ(5, countMetrics.data_size());
+
+    auto data = countMetrics.data(0);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(1);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(2);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    // Partial bucket as metric is deactivated.
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(3);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 13,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(4);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 13,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
+TEST(MetricActivationE2eTest, TestCountMetricWithTwoDeactivations) {
+    auto config = CreateStatsdConfigWithTwoDeactivations();
+
+    int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs
+    int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
+
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    sp<UidMap> m = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> subscriberAlarmMonitor;
+    vector<int64_t> activeConfigsBroadcast;
+
+    long timeBase1 = 1;
+    int broadcastCount = 0;
+    StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor,
+            bucketStartTimeNs, [](const ConfigKey& key) { return true; },
+            [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid,
+                    const vector<int64_t>& activeConfigs) {
+                broadcastCount++;
+                EXPECT_EQ(broadcastUid, uid);
+                activeConfigsBroadcast.clear();
+                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(),
+                        activeConfigs.begin(), activeConfigs.end());
+                return true;
+            });
+
+    processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
+
+    EXPECT_EQ(processor.mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    auto& eventActivationMap = metricProducer->mEventActivationMap;
+    auto& eventDeactivationMap = metricProducer->mEventDeactivationMap;
+
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_FALSE(metricProducer->mIsActive);
+    // Two activations: one is triggered by battery saver mode (tracker index 0), the other is
+    // triggered by screen on event (tracker index 2).
+    EXPECT_EQ(eventActivationMap.size(), 2u);
+    EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end());
+    EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap.size(), 2u);
+    EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end());
+    EXPECT_TRUE(eventDeactivationMap.find(4) != eventDeactivationMap.end());
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+
+    std::unique_ptr<LogEvent> event;
+
+    event = CreateAppCrashEvent(111, bucketStartTimeNs + 5);
+    processor.OnLogEvent(event.get());
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_FALSE(metricProducer->mIsActive);
+    EXPECT_EQ(broadcastCount, 0);
+
+    // Activated by battery save mode.
+    event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(broadcastCount, 1);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+
+    // First processed event.
+    event = CreateAppCrashEvent(222, bucketStartTimeNs + 15);
+    processor.OnLogEvent(event.get());
+
+    // Activated by screen on event.
+    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+                                          bucketStartTimeNs + 20);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+
+    // 2nd processed event.
+    // The activation by screen_on event expires, but the one by battery save mode is still active.
+    event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+    // No new broadcast since the config should still be active.
+    EXPECT_EQ(broadcastCount, 1);
+
+    // 3rd processed event.
+    event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
+    processor.OnLogEvent(event.get());
+
+    // All activations expired.
+    event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8);
+    processor.OnLogEvent(event.get());
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_FALSE(metricProducer->mIsActive);
+    // New broadcast since the config is no longer active.
+    EXPECT_EQ(broadcastCount, 2);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+
+    // Re-activate metric via screen on.
+    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+                                          bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(broadcastCount, 3);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+
+    // 4th processed event.
+    event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
+    processor.OnLogEvent(event.get());
+
+    // Re-enable battery saver mode activation.
+    event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(broadcastCount, 3);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+
+    // 5th processed event.
+    event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
+    processor.OnLogEvent(event.get());
+
+    // Cancel battery saver mode and screen on activation.
+    event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
+    processor.OnLogEvent(event.get());
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_FALSE(metricProducer->mIsActive);
+    // New broadcast since the config is no longer active.
+    EXPECT_EQ(broadcastCount, 4);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+
+    // Screen-on activation expired.
+    event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13);
+    processor.OnLogEvent(event.get());
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_FALSE(metricProducer->mIsActive);
+    EXPECT_EQ(broadcastCount, 4);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+
+    event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
+    processor.OnLogEvent(event.get());
+
+    // Re-enable battery saver mode activation.
+    event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(broadcastCount, 5);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+
+    // Cancel battery saver mode and screen on activation.
+    event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16);
+    processor.OnLogEvent(event.get());
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_FALSE(metricProducer->mIsActive);
+    EXPECT_EQ(broadcastCount, 6);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true,
+                            ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStartEndTimestamp(&reports);
+    EXPECT_EQ(1, reports.reports_size());
+    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size());
+
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(
+            reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    EXPECT_EQ(5, countMetrics.data_size());
+
+    auto data = countMetrics.data(0);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(1);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(2);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    // Partial bucket as metric is deactivated.
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(3);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(4);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
+TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations) {
+    auto config = CreateStatsdConfigWithTwoMetricsTwoDeactivations();
+
+    int64_t bucketStartTimeNs = NS_PER_SEC * 10; // 10 secs
+    int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
+
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    sp<UidMap> m = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> subscriberAlarmMonitor;
+    vector<int64_t> activeConfigsBroadcast;
+
+    long timeBase1 = 1;
+    int broadcastCount = 0;
+    StatsLogProcessor processor(m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor,
+            bucketStartTimeNs, [](const ConfigKey& key) { return true; },
+            [&uid, &broadcastCount, &activeConfigsBroadcast](const int& broadcastUid,
+                    const vector<int64_t>& activeConfigs) {
+                broadcastCount++;
+                EXPECT_EQ(broadcastUid, uid);
+                activeConfigsBroadcast.clear();
+                activeConfigsBroadcast.insert(activeConfigsBroadcast.end(),
+                        activeConfigs.begin(), activeConfigs.end());
+                return true;
+            });
+
+    processor.OnConfigUpdated(bucketStartTimeNs, cfgKey, config);
+
+    EXPECT_EQ(processor.mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor.mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 2);
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    auto& eventActivationMap = metricProducer->mEventActivationMap;
+    auto& eventDeactivationMap = metricProducer->mEventDeactivationMap;
+    sp<MetricProducer> metricProducer2 = metricsManager->mAllMetricProducers[1];
+    auto& eventActivationMap2 = metricProducer2->mEventActivationMap;
+    auto& eventDeactivationMap2 = metricProducer2->mEventDeactivationMap;
+
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_FALSE(metricProducer->mIsActive);
+    EXPECT_FALSE(metricProducer2->mIsActive);
+    // Two activations: one is triggered by battery saver mode (tracker index 0), the other is
+    // triggered by screen on event (tracker index 2).
+    EXPECT_EQ(eventActivationMap.size(), 2u);
+    EXPECT_TRUE(eventActivationMap.find(0) != eventActivationMap.end());
+    EXPECT_TRUE(eventActivationMap.find(2) != eventActivationMap.end());
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap.size(), 2u);
+    EXPECT_TRUE(eventDeactivationMap.find(3) != eventDeactivationMap.end());
+    EXPECT_TRUE(eventDeactivationMap.find(4) != eventDeactivationMap.end());
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+
+    EXPECT_EQ(eventActivationMap2.size(), 2u);
+    EXPECT_TRUE(eventActivationMap2.find(0) != eventActivationMap2.end());
+    EXPECT_TRUE(eventActivationMap2.find(2) != eventActivationMap2.end());
+    EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap2[0]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap2[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap2.size(), 2u);
+    EXPECT_TRUE(eventDeactivationMap2.find(3) != eventDeactivationMap2.end());
+    EXPECT_TRUE(eventDeactivationMap2.find(4) != eventDeactivationMap2.end());
+    EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
+    EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
+
+    std::unique_ptr<LogEvent> event;
+
+    event = CreateAppCrashEvent(111, bucketStartTimeNs + 5);
+    processor.OnLogEvent(event.get());
+    event = CreateMoveToForegroundEvent(1111, bucketStartTimeNs + 5);
+    processor.OnLogEvent(event.get());
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_FALSE(metricProducer->mIsActive);
+    EXPECT_FALSE(metricProducer2->mIsActive);
+    EXPECT_EQ(broadcastCount, 0);
+
+    // Activated by battery save mode.
+    event = CreateBatterySaverOnEvent(bucketStartTimeNs + 10);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_EQ(broadcastCount, 1);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+    EXPECT_TRUE(metricProducer2->mIsActive);
+    EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap2[2]->activation_ns, 0);
+    EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
+    EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
+
+    // First processed event.
+    event = CreateAppCrashEvent(222, bucketStartTimeNs + 15);
+    processor.OnLogEvent(event.get());
+    event = CreateMoveToForegroundEvent(2222, bucketStartTimeNs + 15);
+    processor.OnLogEvent(event.get());
+
+    // Activated by screen on event.
+    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+                                          bucketStartTimeNs + 20);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+    EXPECT_TRUE(metricProducer2->mIsActive);
+    EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
+    EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
+
+    // 2nd processed event.
+    // The activation by screen_on event expires, but the one by battery save mode is still active.
+    event = CreateAppCrashEvent(333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
+    processor.OnLogEvent(event.get());
+    event = CreateMoveToForegroundEvent(3333, bucketStartTimeNs + NS_PER_SEC * 60 * 2 + 25);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+    EXPECT_TRUE(metricProducer2->mIsActive);
+    EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
+    EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
+    // No new broadcast since the config should still be active.
+    EXPECT_EQ(broadcastCount, 1);
+
+    // 3rd processed event.
+    event = CreateAppCrashEvent(444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
+    processor.OnLogEvent(event.get());
+    event = CreateMoveToForegroundEvent(4444, bucketStartTimeNs + NS_PER_SEC * 60 * 5 + 25);
+    processor.OnLogEvent(event.get());
+
+    // All activations expired.
+    event = CreateAppCrashEvent(555, bucketStartTimeNs + NS_PER_SEC * 60 * 8);
+    processor.OnLogEvent(event.get());
+    event = CreateMoveToForegroundEvent(5555, bucketStartTimeNs + NS_PER_SEC * 60 * 8);
+    processor.OnLogEvent(event.get());
+    EXPECT_FALSE(metricsManager->isActive());
+    // New broadcast since the config is no longer active.
+    EXPECT_EQ(broadcastCount, 2);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    EXPECT_FALSE(metricProducer->mIsActive);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+    EXPECT_FALSE(metricProducer2->mIsActive);
+    EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + 20);
+    EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
+    EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
+
+    // Re-activate metric via screen on.
+    event = CreateScreenStateChangedEvent(android::view::DISPLAY_STATE_ON,
+                                          bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_EQ(broadcastCount, 3);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+    EXPECT_TRUE(metricProducer2->mIsActive);
+    EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + 10);
+    EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
+    EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
+
+    // 4th processed event.
+    event = CreateAppCrashEvent(666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
+    processor.OnLogEvent(event.get());
+    event = CreateMoveToForegroundEvent(6666, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 1);
+    processor.OnLogEvent(event.get());
+
+    // Re-enable battery saver mode activation.
+    event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_EQ(broadcastCount, 3);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+    EXPECT_TRUE(metricProducer2->mIsActive);
+    EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
+    EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
+
+    // 5th processed event.
+    event = CreateAppCrashEvent(777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
+    processor.OnLogEvent(event.get());
+    event = CreateMoveToForegroundEvent(7777, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 40);
+    processor.OnLogEvent(event.get());
+
+    // Cancel battery saver mode and screen on activation.
+    event = CreateScreenBrightnessChangedEvent(64, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 60);
+    processor.OnLogEvent(event.get());
+    EXPECT_FALSE(metricsManager->isActive());
+    // New broadcast since the config is no longer active.
+    EXPECT_EQ(broadcastCount, 4);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    EXPECT_FALSE(metricProducer->mIsActive);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+    EXPECT_FALSE(metricProducer2->mIsActive);
+    EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
+    EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
+
+    // Screen-on activation expired.
+    event = CreateAppCrashEvent(888, bucketStartTimeNs + NS_PER_SEC * 60 * 13);
+    processor.OnLogEvent(event.get());
+    event = CreateMoveToForegroundEvent(8888, bucketStartTimeNs + NS_PER_SEC * 60 * 13);
+    processor.OnLogEvent(event.get());
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_EQ(broadcastCount, 4);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    EXPECT_FALSE(metricProducer->mIsActive);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+    EXPECT_FALSE(metricProducer2->mIsActive);
+    EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 11 + 15);
+    EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
+    EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
+
+    event = CreateAppCrashEvent(999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
+    processor.OnLogEvent(event.get());
+    event = CreateMoveToForegroundEvent(9999, bucketStartTimeNs + NS_PER_SEC * 60 * 14 + 1);
+    processor.OnLogEvent(event.get());
+
+    // Re-enable battery saver mode activation.
+    event = CreateBatterySaverOnEvent(bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    processor.OnLogEvent(event.get());
+    EXPECT_TRUE(metricsManager->isActive());
+    EXPECT_EQ(broadcastCount, 5);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 1);
+    EXPECT_EQ(activeConfigsBroadcast[0], cfgId);
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+    EXPECT_TRUE(metricProducer2->mIsActive);
+    EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kActive);
+    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
+    EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
+
+    // Cancel battery saver mode and screen on activation.
+    event = CreateScreenBrightnessChangedEvent(140, bucketStartTimeNs + NS_PER_SEC * 60 * 16);
+    processor.OnLogEvent(event.get());
+    EXPECT_FALSE(metricsManager->isActive());
+    EXPECT_EQ(broadcastCount, 6);
+    EXPECT_EQ(activeConfigsBroadcast.size(), 0);
+    EXPECT_FALSE(metricProducer->mIsActive);
+    EXPECT_EQ(eventActivationMap[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap[3], eventActivationMap[0]);
+    EXPECT_EQ(eventDeactivationMap[4], eventActivationMap[2]);
+    EXPECT_FALSE(metricProducer2->mIsActive);
+    EXPECT_EQ(eventActivationMap2[0]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap2[0]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 15);
+    EXPECT_EQ(eventActivationMap2[0]->ttl_ns, 60 * 6 * NS_PER_SEC);
+    EXPECT_EQ(eventActivationMap2[2]->state, ActivationState::kNotActive);
+    EXPECT_EQ(eventActivationMap2[2]->activation_ns, bucketStartTimeNs + NS_PER_SEC * 60 * 10 + 10);
+    EXPECT_EQ(eventActivationMap2[2]->ttl_ns, 60 * 2 * NS_PER_SEC);
+    EXPECT_EQ(eventDeactivationMap2[3], eventActivationMap2[0]);
+    EXPECT_EQ(eventDeactivationMap2[4], eventActivationMap2[2]);
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor.onDumpReport(cfgKey, bucketStartTimeNs + NS_PER_SEC * 60 * 15 + 1, false, true,
+                            ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStartEndTimestamp(&reports);
+    EXPECT_EQ(1, reports.reports_size());
+    EXPECT_EQ(2, reports.reports(0).metrics_size());
+    EXPECT_EQ(5, reports.reports(0).metrics(0).count_metrics().data_size());
+    EXPECT_EQ(5, reports.reports(0).metrics(1).count_metrics().data_size());
+
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+
+    sortMetricDataByDimensionsValue(
+            reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    EXPECT_EQ(5, countMetrics.data_size());
+
+    auto data = countMetrics.data(0);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(1);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(2);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    // Partial bucket as metric is deactivated.
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(3);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(4);
+    EXPECT_EQ(android::util::PROCESS_LIFE_CYCLE_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+
+
+   countMetrics.clear_data();
+    sortMetricDataByDimensionsValue(
+            reports.reports(0).metrics(1).count_metrics(), &countMetrics);
+    EXPECT_EQ(5, countMetrics.data_size());
+
+    data = countMetrics.data(0);
+    EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(2222, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(1);
+    EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(3333, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(2);
+    EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(4444, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    // Partial bucket as metric is deactivated.
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 8,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(3);
+    EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(6666, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = countMetrics.data(4);
+    EXPECT_EQ(android::util::ACTIVITY_FOREGROUND_STATE_CHANGED, data.dimensions_in_what().field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1 /* uid field */,
+              data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(7777, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(1, data.bucket_info(0).count());
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
+              data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(bucketStartTimeNs + NS_PER_SEC * 60 * 11,
+              data.bucket_info(0).end_bucket_elapsed_nanos());
+}
 
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 80b6349..395c867 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -513,71 +513,75 @@
     /** @hide Process is hosting a foreground service with location type. */
     public static final int PROCESS_STATE_FOREGROUND_SERVICE_LOCATION = 3;
 
+    /** @hide Process is bound to a TOP app. This is ranked below SERVICE_LOCATION so that
+     * it doesn't get the capability of location access while-in-use. */
+    public static final int PROCESS_STATE_BOUND_TOP = 4;
+
     /** @hide Process is hosting a foreground service. */
     @UnsupportedAppUsage
-    public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;
+    public static final int PROCESS_STATE_FOREGROUND_SERVICE = 5;
 
     /** @hide Process is hosting a foreground service due to a system binding. */
     @UnsupportedAppUsage
-    public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 5;
+    public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 6;
 
     /** @hide Process is important to the user, and something they are aware of. */
-    public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 6;
+    public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 7;
 
     /** @hide Process is important to the user, but not something they are aware of. */
     @UnsupportedAppUsage
-    public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 7;
+    public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 8;
 
     /** @hide Process is in the background transient so we will try to keep running. */
-    public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 8;
+    public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 9;
 
     /** @hide Process is in the background running a backup/restore operation. */
-    public static final int PROCESS_STATE_BACKUP = 9;
+    public static final int PROCESS_STATE_BACKUP = 10;
 
     /** @hide Process is in the background running a service.  Unlike oom_adj, this level
      * is used for both the normal running in background state and the executing
      * operations state. */
     @UnsupportedAppUsage
-    public static final int PROCESS_STATE_SERVICE = 10;
+    public static final int PROCESS_STATE_SERVICE = 11;
 
     /** @hide Process is in the background running a receiver.   Note that from the
      * perspective of oom_adj, receivers run at a higher foreground level, but for our
      * prioritization here that is not necessary and putting them below services means
      * many fewer changes in some process states as they receive broadcasts. */
     @UnsupportedAppUsage
-    public static final int PROCESS_STATE_RECEIVER = 11;
+    public static final int PROCESS_STATE_RECEIVER = 12;
 
     /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
-    public static final int PROCESS_STATE_TOP_SLEEPING = 12;
+    public static final int PROCESS_STATE_TOP_SLEEPING = 13;
 
     /** @hide Process is in the background, but it can't restore its state so we want
      * to try to avoid killing it. */
-    public static final int PROCESS_STATE_HEAVY_WEIGHT = 13;
+    public static final int PROCESS_STATE_HEAVY_WEIGHT = 14;
 
     /** @hide Process is in the background but hosts the home activity. */
     @UnsupportedAppUsage
-    public static final int PROCESS_STATE_HOME = 14;
+    public static final int PROCESS_STATE_HOME = 15;
 
     /** @hide Process is in the background but hosts the last shown activity. */
-    public static final int PROCESS_STATE_LAST_ACTIVITY = 15;
+    public static final int PROCESS_STATE_LAST_ACTIVITY = 16;
 
     /** @hide Process is being cached for later use and contains activities. */
     @UnsupportedAppUsage
-    public static final int PROCESS_STATE_CACHED_ACTIVITY = 16;
+    public static final int PROCESS_STATE_CACHED_ACTIVITY = 17;
 
     /** @hide Process is being cached for later use and is a client of another cached
      * process that contains activities. */
-    public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 17;
+    public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 18;
 
     /** @hide Process is being cached for later use and has an activity that corresponds
      * to an existing recent task. */
-    public static final int PROCESS_STATE_CACHED_RECENT = 18;
+    public static final int PROCESS_STATE_CACHED_RECENT = 19;
 
     /** @hide Process is being cached for later use and is empty. */
-    public static final int PROCESS_STATE_CACHED_EMPTY = 19;
+    public static final int PROCESS_STATE_CACHED_EMPTY = 20;
 
     /** @hide Process does not exist. */
-    public static final int PROCESS_STATE_NONEXISTENT = 20;
+    public static final int PROCESS_STATE_NONEXISTENT = 21;
 
     // NOTE: If PROCESS_STATEs are added, then new fields must be added
     // to frameworks/base/core/proto/android/app/enums.proto and the following method must
@@ -602,6 +606,8 @@
                 return AppProtoEnums.PROCESS_STATE_PERSISTENT_UI;
             case PROCESS_STATE_TOP:
                 return AppProtoEnums.PROCESS_STATE_TOP;
+            case PROCESS_STATE_BOUND_TOP:
+                return AppProtoEnums.PROCESS_STATE_BOUND_TOP;
             case PROCESS_STATE_FOREGROUND_SERVICE_LOCATION:
             case PROCESS_STATE_FOREGROUND_SERVICE:
                 return AppProtoEnums.PROCESS_STATE_FOREGROUND_SERVICE;
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 7eab5db..4ef554d 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -19,6 +19,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -146,6 +147,7 @@
         return IActivityTaskManagerSingleton.get();
     }
 
+    @UnsupportedAppUsage(trackingBug = 129726065)
     private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
             new Singleton<IActivityTaskManager>() {
                 @Override
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b2b1e77..38006dc 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -5920,6 +5920,10 @@
                                                 UserHandle.myUserId());
         VMRuntime.setProcessPackageName(data.appInfo.packageName);
 
+        // Pass data directory path to ART. This is used for caching information and
+        // should be set before any application code is loaded.
+        VMRuntime.setProcessDataDirectory(data.appInfo.dataDir);
+
         if (mProfiler.profileFd != null) {
             mProfiler.startProfiling();
         }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 8a52265..83c5e20 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2147,8 +2147,8 @@
         public static final int UPDATE_ERROR_INCORRECT_OS_VERSION = 2;
 
         /**
-         * Represents the update file being wrong, i.e. payloads are mismatched, wrong compressions
-         * method.
+         * Represents the update file being wrong; e.g. payloads are mismatched, or the wrong
+         * compression method is used.
          */
         public static final int UPDATE_ERROR_UPDATE_FILE_INVALID = 3;
 
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d7a2e1b..af738da 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -427,7 +427,7 @@
      * invisible background activities.  This will impact the number of
      * recent activities the user can switch between without having them
      * restart.  There is no guarantee this will be respected, as the system
-     * tries to balance such requests from one app vs. the importantance of
+     * tries to balance such requests from one app vs. the importance of
      * keeping other apps around.
      */
     public static final int BIND_VISIBLE = 0x10000000;
diff --git a/core/java/android/content/LoggingContentInterface.java b/core/java/android/content/LoggingContentInterface.java
index 6e12a57..83c0c91 100644
--- a/core/java/android/content/LoggingContentInterface.java
+++ b/core/java/android/content/LoggingContentInterface.java
@@ -48,191 +48,197 @@
         this.delegate = delegate;
     }
 
-    private void log(String method, Object res, Object... args) {
-        // First, force-unparcel any bundles so we can log them
-        for (Object arg : args) {
-            if (arg instanceof Bundle) {
-                ((Bundle) arg).size();
+    private class Logger implements AutoCloseable {
+        private final StringBuilder sb = new StringBuilder();
+
+        public Logger(String method, Object... args) {
+            // First, force-unparcel any bundles so we can log them
+            for (Object arg : args) {
+                if (arg instanceof Bundle) {
+                    ((Bundle) arg).size();
+                }
+            }
+
+            sb.append("callingUid=").append(Binder.getCallingUid()).append(' ');
+            sb.append(method);
+            sb.append('(').append(deepToString(args)).append(')');
+        }
+
+        private String deepToString(Object value) {
+            if (value != null && value.getClass().isArray()) {
+                return Arrays.deepToString((Object[]) value);
+            } else {
+                return String.valueOf(value);
             }
         }
 
-        final StringBuilder sb = new StringBuilder();
-        sb.append("callingUid=").append(Binder.getCallingUid()).append(' ');
-        sb.append(method);
-        sb.append('(').append(deepToString(args)).append(')');
-        if (res instanceof Cursor) {
-            sb.append('\n');
-            DatabaseUtils.dumpCursor((Cursor) res, sb);
-        } else {
-            sb.append(" = ").append(deepToString(res));
+        public <T> T setResult(T res) {
+            if (res instanceof Cursor) {
+                sb.append('\n');
+                DatabaseUtils.dumpCursor((Cursor) res, sb);
+            } else {
+                sb.append(" = ").append(deepToString(res));
+            }
+            return res;
         }
 
-        if (res instanceof Exception) {
-            Log.e(tag, sb.toString());
-        } else {
+        @Override
+        public void close() {
             Log.v(tag, sb.toString());
         }
     }
 
-    private String deepToString(Object value) {
-        if (value != null && value.getClass().isArray()) {
-            return Arrays.deepToString((Object[]) value);
-        } else {
-            return String.valueOf(value);
-        }
-    }
-
     @Override
     public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
             @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal)
             throws RemoteException {
-        try {
-            final Cursor res = delegate.query(uri, projection, queryArgs, cancellationSignal);
-            log("query", res, uri, projection, queryArgs, cancellationSignal);
-            return res;
-        } catch (Exception res) {
-            log("query", res, uri, projection, queryArgs, cancellationSignal);
-            throw res;
+        try (Logger l = new Logger("query", uri, projection, queryArgs, cancellationSignal)) {
+            try {
+                return l.setResult(delegate.query(uri, projection, queryArgs, cancellationSignal));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 
     @Override
     public @Nullable String getType(@NonNull Uri uri) throws RemoteException {
-        try {
-            final String res = delegate.getType(uri);
-            log("getType", res, uri);
-            return res;
-        } catch (Exception res) {
-            log("getType", res, uri);
-            throw res;
+        try (Logger l = new Logger("getType", uri)) {
+            try {
+                return l.setResult(delegate.getType(uri));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 
     @Override
     public @Nullable String[] getStreamTypes(@NonNull Uri uri, @NonNull String mimeTypeFilter)
             throws RemoteException {
-        try {
-            final String[] res = delegate.getStreamTypes(uri, mimeTypeFilter);
-            log("getStreamTypes", res, uri, mimeTypeFilter);
-            return res;
-        } catch (Exception res) {
-            log("getStreamTypes", res, uri, mimeTypeFilter);
-            throw res;
+        try (Logger l = new Logger("getStreamTypes", uri, mimeTypeFilter)) {
+            try {
+                return l.setResult(delegate.getStreamTypes(uri, mimeTypeFilter));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 
     @Override
     public @Nullable Uri canonicalize(@NonNull Uri uri) throws RemoteException {
-        try {
-            final Uri res = delegate.canonicalize(uri);
-            log("canonicalize", res, uri);
-            return res;
-        } catch (Exception res) {
-            log("canonicalize", res, uri);
-            throw res;
+        try (Logger l = new Logger("canonicalize", uri)) {
+            try {
+                return l.setResult(delegate.canonicalize(uri));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 
     @Override
     public @Nullable Uri uncanonicalize(@NonNull Uri uri) throws RemoteException {
-        try {
-            final Uri res = delegate.uncanonicalize(uri);
-            log("uncanonicalize", res, uri);
-            return res;
-        } catch (Exception res) {
-            log("uncanonicalize", res, uri);
-            throw res;
+        try (Logger l = new Logger("uncanonicalize", uri)) {
+            try {
+                return l.setResult(delegate.uncanonicalize(uri));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 
     @Override
     public boolean refresh(@NonNull Uri uri, @Nullable Bundle args,
             @Nullable CancellationSignal cancellationSignal) throws RemoteException {
-        try {
-            final boolean res = delegate.refresh(uri, args, cancellationSignal);
-            log("refresh", res, uri, args, cancellationSignal);
-            return res;
-        } catch (Exception res) {
-            log("refresh", res, uri, args, cancellationSignal);
-            throw res;
+        try (Logger l = new Logger("refresh", uri, args, cancellationSignal)) {
+            try {
+                return l.setResult(delegate.refresh(uri, args, cancellationSignal));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 
     @Override
     public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues initialValues)
             throws RemoteException {
-        try {
-            final Uri res = delegate.insert(uri, initialValues);
-            log("insert", res, uri, initialValues);
-            return res;
-        } catch (Exception res) {
-            log("insert", res, uri, initialValues);
-            throw res;
+        try (Logger l = new Logger("insert", uri, initialValues)) {
+            try {
+                return l.setResult(delegate.insert(uri, initialValues));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 
     @Override
     public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] initialValues)
             throws RemoteException {
-        try {
-            final int res = delegate.bulkInsert(uri, initialValues);
-            log("bulkInsert", res, uri, initialValues);
-            return res;
-        } catch (Exception res) {
-            log("bulkInsert", res, uri, initialValues);
-            throw res;
+        try (Logger l = new Logger("bulkInsert", uri, initialValues)) {
+            try {
+                return l.setResult(delegate.bulkInsert(uri, initialValues));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 
     @Override
     public int delete(@NonNull Uri uri, @Nullable String selection,
             @Nullable String[] selectionArgs) throws RemoteException {
-        try {
-            final int res = delegate.delete(uri, selection, selectionArgs);
-            log("delete", res, uri, selection, selectionArgs);
-            return res;
-        } catch (Exception res) {
-            log("delete", res, uri, selection, selectionArgs);
-            throw res;
+        try (Logger l = new Logger("delete", uri, selection, selectionArgs)) {
+            try {
+                return l.setResult(delegate.delete(uri, selection, selectionArgs));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 
     @Override
     public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection,
             @Nullable String[] selectionArgs) throws RemoteException {
-        try {
-            final int res = delegate.update(uri, values, selection, selectionArgs);
-            log("update", res, uri, values, selection, selectionArgs);
-            return res;
-        } catch (Exception res) {
-            log("update", res, uri, values, selection, selectionArgs);
-            throw res;
+        try (Logger l = new Logger("update", uri, values, selection, selectionArgs)) {
+            try {
+                return l.setResult(delegate.update(uri, values, selection, selectionArgs));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 
     @Override
     public @Nullable ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode,
             @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
-        try {
-            final ParcelFileDescriptor res = delegate.openFile(uri, mode, signal);
-            log("openFile", res, uri, mode, signal);
-            return res;
-        } catch (Exception res) {
-            log("openFile", res, uri, mode, signal);
-            throw res;
+        try (Logger l = new Logger("openFile", uri, mode, signal)) {
+            try {
+                return l.setResult(delegate.openFile(uri, mode, signal));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 
     @Override
     public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode,
             @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
-        try {
-            final AssetFileDescriptor res = delegate.openAssetFile(uri, mode, signal);
-            log("openAssetFile", res, uri, mode, signal);
-            return res;
-        } catch (Exception res) {
-            log("openAssetFile", res, uri, mode, signal);
-            throw res;
+        try (Logger l = new Logger("openAssetFile", uri, mode, signal)) {
+            try {
+                return l.setResult(delegate.openAssetFile(uri, mode, signal));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 
@@ -240,13 +246,13 @@
     public @Nullable AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri,
             @NonNull String mimeTypeFilter, @Nullable Bundle opts,
             @Nullable CancellationSignal signal) throws RemoteException, FileNotFoundException {
-        try {
-            final AssetFileDescriptor res = delegate.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
-            log("openTypedAssetFile", res, uri, mimeTypeFilter, opts, signal);
-            return res;
-        } catch (Exception res) {
-            log("openTypedAssetFile", res, uri, mimeTypeFilter, opts, signal);
-            throw res;
+        try (Logger l = new Logger("openTypedAssetFile", uri, mimeTypeFilter, opts, signal)) {
+            try {
+                return l.setResult(delegate.openTypedAssetFile(uri, mimeTypeFilter, opts, signal));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 
@@ -254,26 +260,26 @@
     public @NonNull ContentProviderResult[] applyBatch(@NonNull String authority,
             @NonNull ArrayList<ContentProviderOperation> operations)
             throws RemoteException, OperationApplicationException {
-        try {
-            final ContentProviderResult[] res = delegate.applyBatch(authority, operations);
-            log("applyBatch", res, authority, operations);
-            return res;
-        } catch (Exception res) {
-            log("applyBatch", res, authority, operations);
-            throw res;
+        try (Logger l = new Logger("applyBatch", authority, operations)) {
+            try {
+                return l.setResult(delegate.applyBatch(authority, operations));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 
     @Override
     public @Nullable Bundle call(@NonNull String authority, @NonNull String method,
             @Nullable String arg, @Nullable Bundle extras) throws RemoteException {
-        try {
-            final Bundle res = delegate.call(authority, method, arg, extras);
-            log("call", res, authority, method, arg, extras);
-            return res;
-        } catch (Exception res) {
-            log("call", res, authority, method, arg, extras);
-            throw res;
+        try (Logger l = new Logger("call", authority, method, arg, extras)) {
+            try {
+                return l.setResult(delegate.call(authority, method, arg, extras));
+            } catch (Exception res) {
+                l.setResult(res);
+                throw res;
+            }
         }
     }
 }
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index 91424f4..fc79a42 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -255,7 +255,7 @@
      * @hide
      */
     @SystemApi
-    @Nullable
+    @NonNull
     public String getTargetPackageName() {
         return targetPackageName;
     }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 5328dda..deb181f 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -687,6 +687,13 @@
      */
     public static final int PRIVATE_FLAG_ALLOW_EXTERNAL_STORAGE_SANDBOX = 1 << 29;
 
+    /**
+     * Value for {@link #privateFlags}: whether this app is pre-installed on the
+     * ODM partition of the system image.
+     * @hide
+     */
+    public static final int PRIVATE_FLAG_ODM = 1 << 30;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "PRIVATE_FLAG_" }, value = {
             PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE,
@@ -717,6 +724,7 @@
             PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE,
             PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE,
             PRIVATE_FLAG_ALLOW_EXTERNAL_STORAGE_SANDBOX,
+            PRIVATE_FLAG_ODM,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ApplicationInfoPrivateFlags {}
@@ -1093,6 +1101,18 @@
     public String appComponentFactory;
 
     /**
+     * Resource id of {@link com.android.internal.R.styleable.AndroidManifestProvider_icon}
+     * @hide
+     */
+    public int iconRes;
+
+    /**
+     * Resource id of {@link com.android.internal.R.styleable.AndroidManifestProvider_roundIcon}
+     * @hide
+     */
+    public int roundIconRes;
+
+    /**
      * The category of this app. Categories are used to cluster multiple apps
      * together into meaningful groups, such as when summarizing battery,
      * network, or disk usage. Apps should only define this value when they fit
@@ -1571,6 +1591,8 @@
         classLoaderName = orig.classLoaderName;
         splitClassLoaderNames = orig.splitClassLoaderNames;
         appComponentFactory = orig.appComponentFactory;
+        iconRes = orig.iconRes;
+        roundIconRes = orig.roundIconRes;
         compileSdkVersion = orig.compileSdkVersion;
         compileSdkVersionCodename = orig.compileSdkVersionCodename;
         mHiddenApiPolicy = orig.mHiddenApiPolicy;
@@ -1650,6 +1672,8 @@
         dest.writeInt(compileSdkVersion);
         dest.writeString(compileSdkVersionCodename);
         dest.writeString(appComponentFactory);
+        dest.writeInt(iconRes);
+        dest.writeInt(roundIconRes);
         dest.writeInt(mHiddenApiPolicy);
         dest.writeInt(hiddenUntilInstalled ? 1 : 0);
         dest.writeString(zygotePreloadName);
@@ -1724,6 +1748,8 @@
         compileSdkVersion = source.readInt();
         compileSdkVersionCodename = source.readString();
         appComponentFactory = source.readString();
+        iconRes = source.readInt();
+        roundIconRes = source.readInt();
         mHiddenApiPolicy = source.readInt();
         hiddenUntilInstalled = source.readInt() != 0;
         zygotePreloadName = source.readString();
@@ -1970,6 +1996,11 @@
     }
 
     /** @hide */
+    public boolean isOdm() {
+        return (privateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0;
+    }
+
+    /** @hide */
     public boolean isPartiallyDirectBootAware() {
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE) != 0;
     }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index a96316b..ec2e8ca 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -17,6 +17,7 @@
 package android.content.pm;
 
 import android.Manifest;
+import android.annotation.CurrentTimeMillisLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1817,6 +1818,9 @@
         public boolean isCommitted;
 
         /** {@hide} */
+        public long updatedMillis;
+
+        /** {@hide} */
         @UnsupportedAppUsage
         public SessionInfo() {
         }
@@ -2237,6 +2241,15 @@
             return isCommitted;
         }
 
+        /**
+         * The timestamp of the last update that occurred to the session, including changing of
+         * states in case of staged sessions.
+         */
+        @CurrentTimeMillisLong
+        public long getUpdatedMillis() {
+            return updatedMillis;
+        }
+
         @Override
         public int describeContents() {
             return 0;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 93bc6d7..8981000 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -49,6 +49,8 @@
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityTaskManager;
+import android.app.ActivityThread;
+import android.app.ResourcesManager;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -69,6 +71,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PatternMatcher;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
@@ -92,6 +95,7 @@
 import android.util.SparseArray;
 import android.util.TypedValue;
 import android.util.apk.ApkSignatureVerifier;
+import android.view.Display;
 import android.view.Gravity;
 
 import com.android.internal.R;
@@ -320,6 +324,8 @@
     private int mParseError = PackageManager.INSTALL_SUCCEEDED;
 
     private static boolean sCompatibilityModeEnabled = true;
+    private static boolean sUseRoundIcon = false;
+
     private static final int PARSE_DEFAULT_INSTALL_LOCATION =
             PackageInfo.INSTALL_LOCATION_UNSPECIFIED;
     private static final int PARSE_DEFAULT_TARGET_SANDBOX = 1;
@@ -3437,6 +3443,11 @@
         TypedArray sa = res.obtainAttributes(parser,
                 com.android.internal.R.styleable.AndroidManifestApplication);
 
+        ai.iconRes = sa.getResourceId(
+            com.android.internal.R.styleable.AndroidManifestApplication_icon, 0);
+        ai.roundIconRes = sa.getResourceId(
+            com.android.internal.R.styleable.AndroidManifestApplication_roundIcon, 0);
+
         if (!parsePackageItemInfo(owner, ai, outError,
                 "<application>", sa, false /*nameRequired*/,
                 com.android.internal.R.styleable.AndroidManifestApplication_name,
@@ -4240,9 +4251,7 @@
             }
         }
 
-        final boolean useRoundIcon =
-                Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useRoundIcon);
-        int roundIconVal = useRoundIcon ? sa.getResourceId(roundIconRes, 0) : 0;
+        int roundIconVal = sUseRoundIcon ? sa.getResourceId(roundIconRes, 0) : 0;
         if (roundIconVal != 0) {
             outInfo.icon = roundIconVal;
             outInfo.nonLocalizedLabel = null;
@@ -5750,9 +5759,7 @@
             outInfo.nonLocalizedLabel = v.coerceToString();
         }
 
-        final boolean useRoundIcon =
-                Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useRoundIcon);
-        int roundIconVal = useRoundIcon ? sa.getResourceId(
+        int roundIconVal = sUseRoundIcon ? sa.getResourceId(
                 com.android.internal.R.styleable.AndroidManifestIntentFilter_roundIcon, 0) : 0;
         if (roundIconVal != 0) {
             outInfo.icon = roundIconVal;
@@ -6920,6 +6927,11 @@
         }
 
         /** @hide */
+        public boolean isOdm() {
+            return applicationInfo.isOdm();
+        }
+
+        /** @hide */
         public boolean isPrivileged() {
             return applicationInfo.isPrivilegedApp();
         }
@@ -7739,6 +7751,7 @@
         }
         ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
         ai.resourceDirs = state.overlayPaths;
+        ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes;
     }
 
     @UnsupportedAppUsage
@@ -8344,6 +8357,39 @@
         sCompatibilityModeEnabled = compatibilityModeEnabled;
     }
 
+    /**
+     * @hide
+     */
+    public static void readConfigUseRoundIcon(Resources r) {
+        if (r != null) {
+            sUseRoundIcon = r.getBoolean(com.android.internal.R.bool.config_useRoundIcon);
+            return;
+        }
+
+        ApplicationInfo androidAppInfo;
+        try {
+            androidAppInfo = ActivityThread.getPackageManager().getApplicationInfo(
+                    "android", 0 /* flags */,
+                UserHandle.myUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        Resources systemResources = Resources.getSystem();
+
+        // Create in-flight as this overlayable resource is only used when config changes
+        Resources overlayableRes = ResourcesManager.getInstance().getResources(null,
+                null,
+                null,
+                androidAppInfo.resourceDirs,
+                androidAppInfo.sharedLibraryFiles,
+                Display.DEFAULT_DISPLAY,
+                null,
+                systemResources.getCompatibilityInfo(),
+                systemResources.getClassLoader());
+
+        sUseRoundIcon = overlayableRes.getBoolean(com.android.internal.R.bool.config_useRoundIcon);
+    }
+
     public static class PackageParserException extends Exception {
         public final int error;
 
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 514015f..e5ef67b 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.annotation.StringRes;
 import android.annotation.StyleRes;
+import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration.NativeConfig;
@@ -357,6 +358,22 @@
         return sEmptyApkAssets;
     }
 
+    /** @hide */
+    @TestApi
+    public @NonNull String[] getApkPaths() {
+        synchronized (this) {
+            if (mOpen) {
+                String[] paths = new String[mApkAssets.length];
+                final int count = mApkAssets.length;
+                for (int i = 0; i < count; i++) {
+                    paths[i] = mApkAssets[i].getAssetPath();
+                }
+                return paths;
+            }
+        }
+        return new String[0];
+    }
+
     /**
      * Returns a cookie for use with the other APIs of AssetManager.
      * @return 0 if the path was not found, otherwise a positive integer cookie representing
@@ -1356,6 +1373,7 @@
     /**
      * @hide
      */
+    @TestApi
     @GuardedBy("this")
     public @Nullable Map<String, String> getOverlayableMap(String packageName) {
         synchronized (this) {
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index 44bd883..647448c 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -61,7 +61,10 @@
 
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
+    // May throw CursorWindowAllocationException
     private static native long nativeCreate(String name, int cursorWindowSize);
+
+    // May throw CursorWindowAllocationException
     private static native long nativeCreateFromParcel(Parcel parcel);
     private static native void nativeDispose(long windowPtr);
     private static native void nativeWriteToParcel(long windowPtr, Parcel parcel);
@@ -135,8 +138,7 @@
         mName = name != null && name.length() != 0 ? name : "<unnamed>";
         mWindowPtr = nativeCreate(mName, (int) windowSizeBytes);
         if (mWindowPtr == 0) {
-            throw new CursorWindowAllocationException("Cursor window allocation of " +
-                    windowSizeBytes + " bytes failed. " + printStats());
+            throw new IllegalStateException(); // Shouldn't happen.
         }
         mCloseGuard.open("close");
         recordNewWindow(Binder.getCallingPid(), mWindowPtr);
@@ -164,8 +166,7 @@
         mStartPos = source.readInt();
         mWindowPtr = nativeCreateFromParcel(source);
         if (mWindowPtr == 0) {
-            throw new CursorWindowAllocationException("Cursor window could not be "
-                    + "created from binder.");
+            throw new IllegalStateException(); // Shouldn't happen.
         }
         mName = nativeGetName(mWindowPtr);
         mCloseGuard.open("close");
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 3e8c334..6035f40 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -45,6 +45,7 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
 
 import java.util.List;
 
@@ -63,6 +64,8 @@
     private static final int MSG_AUTHENTICATION_FAILED = 103;
     private static final int MSG_ERROR = 104;
     private static final int MSG_REMOVED = 105;
+    private static final int MSG_GET_FEATURE_COMPLETED = 106;
+    private static final int MSG_SET_FEATURE_COMPLETED = 107;
 
     private IFaceService mService;
     private final Context mContext;
@@ -70,6 +73,8 @@
     private AuthenticationCallback mAuthenticationCallback;
     private EnrollmentCallback mEnrollmentCallback;
     private RemovalCallback mRemovalCallback;
+    private SetFeatureCallback mSetFeatureCallback;
+    private GetFeatureCallback mGetFeatureCallback;
     private CryptoObject mCryptoObject;
     private Face mRemovalFace;
     private Handler mHandler;
@@ -112,6 +117,20 @@
         public void onEnumerated(long deviceId, int faceId, int remaining) {
             // TODO: Finish. Low priority since it's not used.
         }
+
+        @Override
+        public void onFeatureSet(boolean success, int feature) {
+            mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget();
+        }
+
+        @Override
+        public void onFeatureGet(boolean success, int feature, boolean value) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = success;
+            args.argi1 = feature;
+            args.arg2 = value;
+            mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget();
+        }
     };
 
     /**
@@ -286,31 +305,31 @@
      * @hide
      */
     @RequiresPermission(MANAGE_BIOMETRIC)
-    public boolean setFeature(int feature, boolean enabled, byte[] token) {
+    public void setFeature(int feature, boolean enabled, byte[] token,
+            SetFeatureCallback callback) {
         if (mService != null) {
             try {
-                return mService.setFeature(feature, enabled, token);
+                mSetFeatureCallback = callback;
+                mService.setFeature(feature, enabled, token, mServiceReceiver);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
         }
-        return false;
     }
 
     /**
      * @hide
      */
     @RequiresPermission(MANAGE_BIOMETRIC)
-    public boolean getFeature(int feature) {
-        boolean result = true;
+    public void getFeature(int feature, GetFeatureCallback callback) {
         if (mService != null) {
             try {
-                result = mService.getFeature(feature);
+                mGetFeatureCallback = callback;
+                mService.getFeature(feature, mServiceReceiver);
             } catch (RemoteException e) {
                 throw e.rethrowFromSystemServer();
             }
         }
-        return result;
     }
 
     /**
@@ -874,6 +893,20 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    public abstract static class SetFeatureCallback {
+        public abstract void onCompleted(boolean success, int feature);
+    }
+
+    /**
+     * @hide
+     */
+    public abstract static class GetFeatureCallback {
+        public abstract void onCompleted(boolean success, int feature, boolean value);
+    }
+
     private class OnEnrollCancelListener implements OnCancelListener {
         @Override
         public void onCancel() {
@@ -926,9 +959,36 @@
                 case MSG_REMOVED:
                     sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */);
                     break;
+                case MSG_SET_FEATURE_COMPLETED:
+                    sendSetFeatureCompleted((boolean) msg.obj /* success */,
+                            msg.arg1 /* feature */);
+                    break;
+                case MSG_GET_FEATURE_COMPLETED:
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    sendGetFeatureCompleted((boolean) args.arg1 /* success */,
+                            args.argi1 /* feature */,
+                            (boolean) args.arg2 /* value */);
+                    args.recycle();
+                    break;
+                default:
+                    Log.w(TAG, "Unknown message: " + msg.what);
             }
         }
-    };
+    }
+
+    private void sendSetFeatureCompleted(boolean success, int feature) {
+        if (mSetFeatureCallback == null) {
+            return;
+        }
+        mSetFeatureCallback.onCompleted(success, feature);
+    }
+
+    private void sendGetFeatureCompleted(boolean success, int feature, boolean value) {
+        if (mGetFeatureCallback == null) {
+            return;
+        }
+        mGetFeatureCallback.onCompleted(success, feature, value);
+    }
 
     private void sendRemovedResult(Face face, int remaining) {
         if (mRemovalCallback == null) {
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 5043d4c..601be75 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -51,7 +51,7 @@
 
     // Start face enrollment
     void enroll(IBinder token, in byte [] cryptoToken, IFaceServiceReceiver receiver,
-                String opPackageName, in int [] disabledFeatures);
+            String opPackageName, in int [] disabledFeatures);
 
     // Cancel enrollment in progress
     void cancelEnrollment(IBinder token);
@@ -98,9 +98,10 @@
     // Enumerate all faces
     void enumerate(IBinder token, int userId, IFaceServiceReceiver receiver);
 
-    boolean setFeature(int feature, boolean enabled, in byte [] token);
+    void setFeature(int feature, boolean enabled, in byte [] token,
+            IFaceServiceReceiver receiver);
 
-    boolean getFeature(int feature);
+    void getFeature(int feature, IFaceServiceReceiver receiver);
 
     void userActivity();
 }
diff --git a/core/java/android/hardware/face/IFaceServiceReceiver.aidl b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
index cec9fd8..2176902 100644
--- a/core/java/android/hardware/face/IFaceServiceReceiver.aidl
+++ b/core/java/android/hardware/face/IFaceServiceReceiver.aidl
@@ -29,4 +29,6 @@
     void onError(long deviceId, int error, int vendorCode);
     void onRemoved(long deviceId, int faceId, int remaining);
     void onEnumerated(long deviceId, int faceId, int remaining);
+    void onFeatureSet(boolean success, int feature);
+    void onFeatureGet(boolean success, int feature, boolean value);
 }
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 2923bbf..0daf30f25 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -16,6 +16,7 @@
 
 package android.hardware.input;
 
+import android.graphics.Rect;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.IInputDevicesChangedListener;
@@ -24,6 +25,7 @@
 import android.os.IBinder;
 import android.view.InputDevice;
 import android.view.InputEvent;
+import android.view.InputMonitor;
 import android.view.PointerIcon;
 
 /** @hide */
@@ -82,4 +84,7 @@
     void setCustomPointerIcon(in PointerIcon icon);
 
     void requestPointerCapture(IBinder windowToken, boolean enabled);
+
+    /** Create an input monitor for gestures. */
+    InputMonitor monitorGestureInput(String name, int displayId);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index fec5c34..2a59be2 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -41,6 +41,7 @@
 import android.util.SparseArray;
 import android.view.InputDevice;
 import android.view.InputEvent;
+import android.view.InputMonitor;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 
@@ -933,6 +934,19 @@
         }
     }
 
+    /**
+     * Monitor input on the specified display for gestures.
+     *
+     * @hide
+     */
+    public InputMonitor monitorGestureInput(String name, int displayId) {
+        try {
+            return mIm.monitorGestureInput(name, displayId);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
     private void populateInputDevicesLocked() {
         if (mInputDevicesChangedListener == null) {
             final InputDevicesChangedListener listener = new InputDevicesChangedListener();
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index cf3395a..5b5bd766 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -25,6 +25,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.UnsupportedAppUsage;
+import android.app.ActivityThread;
 import android.media.AudioFormat;
 import android.os.Handler;
 import android.os.Parcel;
@@ -1440,6 +1441,17 @@
     public static final int SERVICE_STATE_DISABLED = 1;
 
     /**
+     * @return returns current package name.
+     */
+    static String getCurrentOpPackageName() {
+        String packageName = ActivityThread.currentOpPackageName();
+        if (packageName == null) {
+            return "";
+        }
+        return packageName;
+    }
+
+    /**
      * Returns a list of descriptors for all hardware modules loaded.
      * @param modules A ModuleProperties array where the list will be returned.
      * @return - {@link #STATUS_OK} in case of success
@@ -1452,7 +1464,23 @@
      * @hide
      */
     @UnsupportedAppUsage
-    public static native int listModules(ArrayList <ModuleProperties> modules);
+    public static int listModules(ArrayList<ModuleProperties> modules) {
+        return listModules(getCurrentOpPackageName(), modules);
+    }
+
+    /**
+     * Returns a list of descriptors for all hardware modules loaded.
+     * @param opPackageName
+     * @param modules A ModuleProperties array where the list will be returned.
+     * @return - {@link #STATUS_OK} in case of success
+     *         - {@link #STATUS_ERROR} in case of unspecified error
+     *         - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission
+     *         - {@link #STATUS_NO_INIT} if the native service cannot be reached
+     *         - {@link #STATUS_BAD_VALUE} if modules is null
+     *         - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails
+     */
+    private static native int listModules(String opPackageName,
+                                          ArrayList<ModuleProperties> modules);
 
     /**
      * Get an interface on a hardware module to control sound models and recognition on
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 402c228..9113548 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -46,9 +46,10 @@
     SoundTriggerModule(int moduleId, SoundTrigger.StatusListener listener, Handler handler) {
         mId = moduleId;
         mEventHandlerDelegate = new NativeEventHandlerDelegate(listener, handler);
-        native_setup(new WeakReference<SoundTriggerModule>(this));
+        native_setup(SoundTrigger.getCurrentOpPackageName(),
+                new WeakReference<SoundTriggerModule>(this));
     }
-    private native void native_setup(Object module_this);
+    private native void native_setup(String opPackageName, Object moduleThis);
 
     @Override
     protected void finalize() {
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index 1339432..a66fcae 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -137,6 +137,8 @@
 
     /**
      * Log a captive portal login event.
+     * @param eventId one of the CAPTIVE_PORTAL_LOGIN_* constants in metrics_constants.proto.
+     * @param packageName captive portal application package name.
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 6ba4a30..2906710 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -2617,7 +2617,7 @@
 
     /**
      * Start listening to tethering change events. Any new added callback will receive the last
-     * tethering status right away. If callback is registered when tethering loses its upstream or
+     * tethering status right away. If callback is registered when tethering has no upstream or
      * disabled, {@link OnTetheringEventCallback#onUpstreamChanged} will immediately be called
      * with a null argument. The same callback object cannot be registered twice.
      *
diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java
index 1a3a6f8..2e52f72 100644
--- a/core/java/android/net/DhcpResults.java
+++ b/core/java/android/net/DhcpResults.java
@@ -72,15 +72,12 @@
      * Create a {@link StaticIpConfiguration} based on the DhcpResults.
      */
     public StaticIpConfiguration toStaticIpConfiguration() {
-        final StaticIpConfiguration s = new StaticIpConfiguration();
-        // All these except dnsServers are immutable, so no need to make copies.
-        s.setIpAddress(ipAddress);
-        s.setGateway(gateway);
-        for (InetAddress addr : dnsServers) {
-            s.addDnsServer(addr);
-        }
-        s.setDomains(domains);
-        return s;
+        return new StaticIpConfiguration.Builder()
+                .setIpAddress(ipAddress)
+                .setGateway(gateway)
+                .setDnsServers(dnsServers)
+                .setDomains(domains)
+                .build();
     }
 
     public DhcpResults(StaticIpConfiguration source) {
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index 565f36f..87f8739 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -22,6 +22,7 @@
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.net.shared.InetAddressUtils;
+import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -33,20 +34,19 @@
 /**
  * Class that describes static IP configuration.
  *
- * This class is different from LinkProperties because it represents
+ * <p>This class is different from {@link LinkProperties} because it represents
  * configuration intent. The general contract is that if we can represent
  * a configuration here, then we should be able to configure it on a network.
  * The intent is that it closely match the UI we have for configuring networks.
  *
- * In contrast, LinkProperties represents current state. It is much more
+ * <p>In contrast, {@link LinkProperties} represents current state. It is much more
  * expressive. For example, it supports multiple IP addresses, multiple routes,
  * stacked interfaces, and so on. Because LinkProperties is so expressive,
  * using it to represent configuration intent as well as current state causes
  * problems. For example, we could unknowingly save a configuration that we are
  * not in fact capable of applying, or we could save a configuration that the
  * UI cannot display, which has the potential for malicious code to hide
- * hostile or unexpected configuration from the user: see, for example,
- * http://b/12663469 and http://b/16893413 .
+ * hostile or unexpected configuration from the user.
  *
  * @hide
  */
@@ -54,24 +54,24 @@
 @TestApi
 public final class StaticIpConfiguration implements Parcelable {
     /** @hide */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     @Nullable
     public LinkAddress ipAddress;
     /** @hide */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     @Nullable
     public InetAddress gateway;
     /** @hide */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     @NonNull
     public final ArrayList<InetAddress> dnsServers;
     /** @hide */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     @Nullable
     public String domains;
 
     public StaticIpConfiguration() {
-        dnsServers = new ArrayList<InetAddress>();
+        dnsServers = new ArrayList<>();
     }
 
     public StaticIpConfiguration(@Nullable StaticIpConfiguration source) {
@@ -92,32 +92,96 @@
         domains = null;
     }
 
+    /**
+     * Get the static IP address included in the configuration.
+     */
     public @Nullable LinkAddress getIpAddress() {
         return ipAddress;
     }
 
-    public void setIpAddress(@Nullable LinkAddress ipAddress) {
-        this.ipAddress = ipAddress;
-    }
-
+    /**
+     * Get the gateway included in the configuration.
+     */
     public @Nullable InetAddress getGateway() {
         return gateway;
     }
 
-    public void setGateway(@Nullable InetAddress gateway) {
-        this.gateway = gateway;
-    }
-
+    /**
+     * Get the DNS servers included in the configuration.
+     */
     public @NonNull List<InetAddress> getDnsServers() {
         return dnsServers;
     }
 
+    /**
+     * Get a {@link String} listing in priority order of the comma separated domains to search when
+     * resolving host names on the link.
+     */
     public @Nullable String getDomains() {
         return domains;
     }
 
-    public void setDomains(@Nullable String newDomains) {
-        domains = newDomains;
+    /**
+     * Helper class to build a new instance of {@link StaticIpConfiguration}.
+     */
+    public static final class Builder {
+        private LinkAddress mIpAddress;
+        private InetAddress mGateway;
+        private Iterable<InetAddress> mDnsServers;
+        private String mDomains;
+
+        /**
+         * Set the IP address to be included in the configuration; null by default.
+         * @return The {@link Builder} for chaining.
+         */
+        public @NonNull Builder setIpAddress(@Nullable LinkAddress ipAddress) {
+            mIpAddress = ipAddress;
+            return this;
+        }
+
+        /**
+         * Set the address of the gateway to be included in the configuration; null by default.
+         * @return The {@link Builder} for chaining.
+         */
+        public @NonNull Builder setGateway(@Nullable InetAddress gateway) {
+            mGateway = gateway;
+            return this;
+        }
+
+        /**
+         * Set the addresses of the DNS servers included in the configuration; empty by default.
+         * @return The {@link Builder} for chaining.
+         */
+        public @NonNull Builder setDnsServers(@NonNull Iterable<InetAddress> dnsServers) {
+            mDnsServers = dnsServers;
+            return this;
+        }
+
+        /**
+         * Sets the DNS domain search path to be used on the link; null by default.
+         * @param newDomains A {@link String} containing the comma separated domains to search when
+         *                   resolving host names on this link, in priority order.
+         * @return The {@link Builder} for chaining.
+         */
+        public @NonNull Builder setDomains(@Nullable String newDomains) {
+            mDomains = newDomains;
+            return this;
+        }
+
+        /**
+         * Create a {@link StaticIpConfiguration} from the parameters in this {@link Builder}.
+         * @return The newly created StaticIpConfiguration.
+         */
+        public @NonNull StaticIpConfiguration build() {
+            final StaticIpConfiguration config = new StaticIpConfiguration();
+            config.ipAddress = mIpAddress;
+            config.gateway = mGateway;
+            for (InetAddress server : mDnsServers) {
+                config.dnsServers.add(server);
+            }
+            config.domains = mDomains;
+            return config;
+        }
     }
 
     /**
@@ -129,16 +193,17 @@
 
     /**
      * Returns the network routes specified by this object. Will typically include a
-     * directly-connected route for the IP address's local subnet and a default route. If the
-     * default gateway is not covered by the directly-connected route, it will also contain a host
-     * route to the gateway as well. This configuration is arguably invalid, but it used to work
-     * in K and earlier, and other OSes appear to accept it.
+     * directly-connected route for the IP address's local subnet and a default route.
+     * @param iface Interface to include in the routes.
      */
     public @NonNull List<RouteInfo> getRoutes(@Nullable String iface) {
         List<RouteInfo> routes = new ArrayList<RouteInfo>(3);
         if (ipAddress != null) {
             RouteInfo connectedRoute = new RouteInfo(ipAddress, null, iface);
             routes.add(connectedRoute);
+            // If the default gateway is not covered by the directly-connected route, also add a
+            // host route to the gateway as well. This configuration is arguably invalid, but it
+            // used to work in K and earlier, and other OSes appear to accept it.
             if (gateway != null && !connectedRoute.matches(gateway)) {
                 routes.add(RouteInfo.makeHostRoute(gateway, iface));
             }
diff --git a/core/java/android/nfc/cardemulation/CardEmulation.java b/core/java/android/nfc/cardemulation/CardEmulation.java
index f23dc2d..c13f64e 100644
--- a/core/java/android/nfc/cardemulation/CardEmulation.java
+++ b/core/java/android/nfc/cardemulation/CardEmulation.java
@@ -34,6 +34,7 @@
 
 import java.util.HashMap;
 import java.util.List;
+import java.util.regex.Pattern;
 
 /**
  * This class can be used to query the state of
@@ -48,6 +49,7 @@
  * on the device.
  */
 public final class CardEmulation {
+    private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
     static final String TAG = "CardEmulation";
 
     /**
@@ -732,7 +734,7 @@
         }
 
         // Verify hex characters
-        if (!aid.matches("[0-9A-Fa-f]{10,32}\\*?\\#?")) {
+        if (!AID_PATTERN.matcher(aid).matches()) {
             Log.e(TAG, "AID " + aid + " is not a valid AID.");
             return false;
         }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index b64fe00..00d522b 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION;
 
 import android.annotation.UnsupportedAppUsage;
@@ -859,7 +860,8 @@
          */
         public static final int[] CRITICAL_PROC_STATES = {
                 PROCESS_STATE_TOP,
-                PROCESS_STATE_FOREGROUND_SERVICE_LOCATION, PROCESS_STATE_FOREGROUND_SERVICE,
+                PROCESS_STATE_FOREGROUND_SERVICE_LOCATION,
+                PROCESS_STATE_BOUND_TOP, PROCESS_STATE_FOREGROUND_SERVICE,
                 PROCESS_STATE_FOREGROUND
         };
 
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index eb91860..66ddf21 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -444,24 +444,20 @@
      *
      * @param workSource The original UID responsible for the binder call.
      * @return token to restore original work source.
-     * @hide
      **/
     @CriticalNative
-    @SystemApi
     public static final native long setCallingWorkSourceUid(int workSource);
 
     /**
      * Returns the work source set by the caller.
      *
      * Unlike {@link Binder#getCallingUid()}, this result of this method cannot be trusted. The
-     * caller can set the value to whatever he wants. Only use this value if you trust the calling
+     * caller can set the value to whatever they want. Only use this value if you trust the calling
      * uid.
      *
      * @return The original UID responsible for the binder transaction.
-     * @hide
      */
     @CriticalNative
-    @SystemApi
     public static final native int getCallingWorkSourceUid();
 
     /**
@@ -484,10 +480,8 @@
      * </pre>
      *
      * @return token to restore original work source.
-     * @hide
      **/
     @CriticalNative
-    @SystemApi
     public static final native long clearCallingWorkSource();
 
     /**
@@ -503,11 +497,8 @@
      *   Binder.restoreCallingWorkSource(token);
      * }
      * </pre>
-     *
-     * @hide
      **/
     @CriticalNative
-    @SystemApi
     public static final native void restoreCallingWorkSource(long token);
 
     /**
diff --git a/core/java/android/os/SELinux.java b/core/java/android/os/SELinux.java
index 8ffafe4..f007dff 100644
--- a/core/java/android/os/SELinux.java
+++ b/core/java/android/os/SELinux.java
@@ -39,6 +39,13 @@
     private static final int SELINUX_ANDROID_RESTORECON_DATADATA = 16;
 
     /**
+     * Get context associated with path by file_contexts.
+     * @param path path to the regular file to get the security context for.
+     * @return a String representing the security context or null on failure.
+     */
+    public static final native String fileSelabelLookup(String path);
+
+    /**
      * Determine whether SELinux is disabled or enabled.
      * @return a boolean indicating whether SELinux is enabled.
      */
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index fa2c480..8f68723 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -35,6 +35,8 @@
 import android.os.Messenger;
 import android.os.ParcelableException;
 import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.FeatureFlagUtils;
 import android.util.Slog;
 
 import java.lang.annotation.Retention;
@@ -315,6 +317,11 @@
      */
     @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM)
     public void bind() {
+        if (!featureFlagEnabled()) {
+            Slog.w(TAG, FeatureFlagUtils.DYNAMIC_SYSTEM + " not enabled; bind() aborted.");
+            return;
+        }
+
         Intent intent = new Intent();
         intent.setClassName("com.android.dynsystem",
                 "com.android.dynsystem.DynamicSystemInstallationService");
@@ -381,6 +388,11 @@
     @RequiresPermission(android.Manifest.permission.INSTALL_DYNAMIC_SYSTEM)
     public void start(@NonNull Uri systemUrl, @BytesLong long systemSize,
             @BytesLong long userdataSize) {
+        if (!featureFlagEnabled()) {
+            Slog.w(TAG, FeatureFlagUtils.DYNAMIC_SYSTEM + " not enabled; start() aborted.");
+            return;
+        }
+
         Intent intent = new Intent();
 
         intent.setClassName("com.android.dynsystem",
@@ -395,6 +407,11 @@
         mContext.startActivity(intent);
     }
 
+    private boolean featureFlagEnabled() {
+        return SystemProperties.getBoolean(
+                FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.DYNAMIC_SYSTEM, false);
+    }
+
     private void handleMessage(Message msg) {
         switch (msg.what) {
             case MSG_POST_STATUS:
diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java
index 0458c2a..cec1945 100644
--- a/core/java/android/os/image/DynamicSystemManager.java
+++ b/core/java/android/os/image/DynamicSystemManager.java
@@ -159,6 +159,16 @@
         }
     }
 
+    /** @return {@code true} if the device has a dynamic system enabled */
+    @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+    public boolean isEnabled() {
+        try {
+            return mService.isEnabled();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
+
     /**
      * Remove DynamicSystem installation if present
      *
@@ -174,14 +184,13 @@
     }
 
     /**
-     * Enable DynamicSystem when it's not enabled, otherwise, disable it.
-     *
+     * Enable or disable DynamicSystem.
      * @return {@code true} if the call succeeds. {@code false} if there is no installed image.
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
-    public boolean toggle() {
+    public boolean setEnable(boolean enable) {
         try {
-            return mService.toggle();
+            return mService.setEnable(enable);
         } catch (RemoteException e) {
             throw new RuntimeException(e.toString());
         }
diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl
index 15f5b68..a34daca 100644
--- a/core/java/android/os/image/IDynamicSystemService.aidl
+++ b/core/java/android/os/image/IDynamicSystemService.aidl
@@ -58,6 +58,11 @@
     boolean isInstalled();
 
     /**
+     * @return true if the device has an DynamicSystem image enabled
+     */
+    boolean isEnabled();
+
+    /**
      * Remove DynamicSystem installation if present
      *
      * @return true if the call succeeds
@@ -65,11 +70,11 @@
     boolean remove();
 
     /**
-     * Enable DynamicSystem when it's not enabled, otherwise, disable it.
+     * Enable or disable DynamicSystem.
      *
      * @return true if the call succeeds
      */
-    boolean toggle();
+    boolean setEnable(boolean enable);
 
     /**
      * Write a chunk of the DynamicSystem system image
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 9085fa2..847b8e4 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -27,6 +27,8 @@
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
+import android.media.audiopolicy.AudioProductStrategies;
+import android.media.audiopolicy.AudioVolumeGroups;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -41,6 +43,7 @@
 import android.widget.SeekBar.OnSeekBarChangeListener;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.SomeArgs;
 
 /**
  * Turns a {@link SeekBar} into a volume control.
@@ -62,6 +65,26 @@
         void onMuted(boolean muted, boolean zenMuted);
     }
 
+    private static final int MSG_GROUP_VOLUME_CHANGED = 1;
+    private final Handler mVolumeHandler = new VolumeHandler();
+    private final AudioProductStrategies mAudioProductStrategies;
+    private AudioAttributes mAttributes;
+    private int mVolumeGroupId;
+
+    private final AudioManager.VolumeGroupCallback mVolumeGroupCallback =
+            new AudioManager.VolumeGroupCallback() {
+        @Override
+        public void onAudioVolumeGroupChanged(int group, int flags) {
+            if (mHandler == null) {
+                return;
+            }
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = group;
+            args.arg2 = flags;
+            mVolumeHandler.sendMessage(mHandler.obtainMessage(MSG_GROUP_VOLUME_CHANGED, args));
+        }
+    };
+
     @UnsupportedAppUsage
     private final Context mContext;
     private final H mUiHandler = new H();
@@ -137,6 +160,15 @@
             mRingerMode = mAudioManager.getRingerModeInternal();
         }
         mZenMode = mNotificationManager.getZenMode();
+
+        mAudioProductStrategies = mAudioManager.getAudioProductStrategies();
+        if (mAudioProductStrategies.size() > 0) {
+            mVolumeGroupId = mAudioProductStrategies.getVolumeGroupIdForLegacyStreamType(
+                    mStreamType);
+            mAttributes = mAudioProductStrategies.getAudioAttributesForLegacyStreamType(
+                    mStreamType);
+        }
+
         mMaxStreamVolume = mAudioManager.getStreamMaxVolume(mStreamType);
         mCallback = callback;
         mOriginalStreamVolume = mAudioManager.getStreamVolume(mStreamType);
@@ -297,6 +329,9 @@
         postStopSample();
         mContext.getContentResolver().unregisterContentObserver(mVolumeObserver);
         mReceiver.setListening(false);
+        if (mAudioProductStrategies.size() > 0) {
+            unregisterVolumeGroupCb();
+        }
         mSeekBar.setOnSeekBarChangeListener(null);
         mHandler.getLooper().quitSafely();
         mHandler = null;
@@ -314,6 +349,9 @@
                 System.getUriFor(System.VOLUME_SETTINGS_INT[mStreamType]),
                 false, mVolumeObserver);
         mReceiver.setListening(true);
+        if (mAudioProductStrategies.size() > 0) {
+            registerVolumeGroupCb();
+        }
     }
 
     public void revertVolume() {
@@ -469,7 +507,9 @@
             if (AudioManager.VOLUME_CHANGED_ACTION.equals(action)) {
                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
                 int streamValue = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, -1);
-                updateVolumeSlider(streamType, streamValue);
+                if (mAudioProductStrategies.size() == 0) {
+                    updateVolumeSlider(streamType, streamValue);
+                }
             } else if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
                 if (mNotificationOrRing) {
                     mRingerMode = mAudioManager.getRingerModeInternal();
@@ -479,8 +519,18 @@
                 }
             } else if (AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) {
                 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
-                int streamVolume = mAudioManager.getStreamVolume(streamType);
-                updateVolumeSlider(streamType, streamVolume);
+                if (mAudioProductStrategies.size() == 0) {
+                    int streamVolume = mAudioManager.getStreamVolume(streamType);
+                    updateVolumeSlider(streamType, streamVolume);
+                } else {
+                    int volumeGroup = mAudioProductStrategies.getVolumeGroupIdForLegacyStreamType(
+                            streamType);
+                    if (volumeGroup != AudioVolumeGroups.DEFAULT_VOLUME_GROUP
+                            && volumeGroup == mVolumeGroupId) {
+                        int streamVolume = mAudioManager.getStreamVolume(streamType);
+                        updateVolumeSlider(streamType, streamVolume);
+                    }
+                }
             } else if (NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED.equals(action)) {
                 mZenMode = mNotificationManager.getZenMode();
                 updateSlider();
@@ -506,4 +556,34 @@
             }
         }
     }
+
+    private void registerVolumeGroupCb() {
+        if (mVolumeGroupId != AudioVolumeGroups.DEFAULT_VOLUME_GROUP) {
+            mAudioManager.registerVolumeGroupCallback(Runnable::run, mVolumeGroupCallback);
+            mLastProgress = mAudioManager.getVolumeIndexForAttributes(mAttributes);
+        }
+    }
+
+    private void unregisterVolumeGroupCb() {
+        if (mVolumeGroupId != AudioVolumeGroups.DEFAULT_VOLUME_GROUP) {
+            mAudioManager.unregisterVolumeGroupCallback(mVolumeGroupCallback);
+        }
+    }
+
+    private class VolumeHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            SomeArgs args = (SomeArgs) msg.obj;
+            switch (msg.what) {
+                case MSG_GROUP_VOLUME_CHANGED:
+                    int group = (int) args.arg1;
+                    if (mVolumeGroupId != group
+                            || mVolumeGroupId == AudioVolumeGroups.DEFAULT_VOLUME_GROUP) {
+                        return;
+                    }
+                    updateSlider();
+                    break;
+            }
+        }
+    }
 }
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index 44adc1c..ef28f07 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -35,7 +35,6 @@
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.DataUsageFeedback;
-import android.telecom.CallIdentification;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -605,69 +604,6 @@
         public static final String BLOCK_REASON = "block_reason";
 
         /**
-         * The package name of the {@link android.telecom.CallScreeningService} which provided
-         * {@link android.telecom.CallIdentification} for this call.
-         * <P>Type: TEXT</P>
-         */
-        public static final String CALL_ID_PACKAGE_NAME = "call_id_package_name";
-
-        /**
-         * The app name of the {@link android.telecom.CallScreeningService} which provided
-         * {@link android.telecom.CallIdentification} for this call.
-         * <P>Type: TEXT</P>
-         */
-        public static final String CALL_ID_APP_NAME = "call_id_app_name";
-
-        /**
-         * The {@link CallIdentification#getName() name} of a call, as provided by the
-         * {@link android.telecom.CallScreeningService}.
-         * <p>
-         * The name is provided by the app identified by {@link #CALL_ID_PACKAGE_NAME} and
-         * {@link #CALL_ID_APP_NAME}.
-         * <P>Type: TEXT</P>
-         */
-        public static final String CALL_ID_NAME = "call_id_name";
-
-        /**
-         * The {@link CallIdentification#getDescription() description} of a call, as provided by the
-         * {@link android.telecom.CallScreeningService}.
-         * <p>
-         * The description is provided by the app identified by {@link #CALL_ID_PACKAGE_NAME} and
-         * {@link #CALL_ID_APP_NAME}.
-         * <P>Type: TEXT</P>
-         */
-        public static final String CALL_ID_DESCRIPTION = "call_id_description";
-
-        /**
-         * The {@link CallIdentification#getDetails() details} of a call, as provided by the
-         * {@link android.telecom.CallScreeningService}.
-         * <p>
-         * The details field is provided by the app identified by {@link #CALL_ID_PACKAGE_NAME} and
-         * {@link #CALL_ID_APP_NAME}.
-         * <P>Type: TEXT</P>
-         */
-        public static final String CALL_ID_DETAILS = "call_id_details";
-
-        /**
-         * The {@link CallIdentification#getNuisanceConfidence() nuisance confidence} of a call, as
-         * provided by the {@link android.telecom.CallScreeningService}.
-         * <p>
-         * Valid values are defined in {@link CallIdentification}, and include:
-         * <ul>
-         *     <li>{@link CallIdentification#CONFIDENCE_NOT_NUISANCE}</li>
-         *     <li>{@link CallIdentification#CONFIDENCE_LIKELY_NOT_NUISANCE}</li>
-         *     <li>{@link CallIdentification#CONFIDENCE_UNKNOWN}</li>
-         *     <li>{@link CallIdentification#CONFIDENCE_LIKELY_NUISANCE}</li>
-         *     <li>{@link CallIdentification#CONFIDENCE_NUISANCE}</li>
-         * </ul>
-         * <p>
-         * The nuisance confidence is provided by the app identified by
-         * {@link #CALL_ID_PACKAGE_NAME} and {@link #CALL_ID_APP_NAME}.
-         * <P>Type: INTEGER</P>
-         */
-        public static final String CALL_ID_NUISANCE_CONFIDENCE = "call_id_nuisance_confidence";
-
-        /**
          * Adds a call to the call log.
          *
          * @param ci the CallerInfo object to get the target contact from.  Can be null
@@ -696,8 +632,7 @@
                 presentation, callType, features, accountHandle, start, duration,
                 dataUsage, false /* addForAllUsers */, null /* userToBeInsertedTo */,
                 false /* isRead */, Calls.BLOCK_REASON_NOT_BLOCKED /* callBlockReason */,
-                null /* callScreeningAppName */, null /* callScreeningComponentName */,
-                null /* callIdentification */);
+                null /* callScreeningAppName */, null /* callScreeningComponentName */);
         }
 
 
@@ -737,8 +672,7 @@
                 features, accountHandle, start, duration, dataUsage, addForAllUsers,
                 userToBeInsertedTo, false /* isRead */ , Calls.BLOCK_REASON_NOT_BLOCKED
                 /* callBlockReason */, null /* callScreeningAppName */,
-                null /* callScreeningComponentName */,
-                null /* callIdentification */);
+                null /* callScreeningComponentName */);
         }
 
         /**
@@ -784,7 +718,7 @@
                 int features, PhoneAccountHandle accountHandle, long start, int duration,
                 Long dataUsage, boolean addForAllUsers, UserHandle userToBeInsertedTo,
                 boolean isRead, int callBlockReason, CharSequence callScreeningAppName,
-                String callScreeningComponentName, CallIdentification callIdentification) {
+                String callScreeningComponentName) {
             if (VERBOSE_LOG) {
                 Log.v(LOG_TAG, String.format("Add call: number=%s, user=%s, for all=%s",
                         number, userToBeInsertedTo, addForAllUsers));
@@ -839,26 +773,6 @@
             values.put(CALL_SCREENING_APP_NAME, charSequenceToString(callScreeningAppName));
             values.put(CALL_SCREENING_COMPONENT_NAME, callScreeningComponentName);
 
-            if (callIdentification != null) {
-                values.put(CALL_ID_PACKAGE_NAME, callIdentification.getCallScreeningPackageName());
-                values.put(CALL_ID_APP_NAME,
-                        charSequenceToString(callIdentification.getCallScreeningAppName()));
-                values.put(CALL_ID_NAME,
-                        charSequenceToString(callIdentification.getName()));
-                values.put(CALL_ID_DESCRIPTION,
-                        charSequenceToString(callIdentification.getDescription()));
-                values.put(CALL_ID_DETAILS,
-                        charSequenceToString(callIdentification.getDetails()));
-                values.put(CALL_ID_NUISANCE_CONFIDENCE, callIdentification.getNuisanceConfidence());
-            } else {
-                values.putNull(CALL_ID_PACKAGE_NAME);
-                values.putNull(CALL_ID_APP_NAME);
-                values.putNull(CALL_ID_NAME);
-                values.putNull(CALL_ID_DESCRIPTION);
-                values.putNull(CALL_ID_DETAILS);
-                values.putNull(CALL_ID_NUISANCE_CONFIDENCE);
-            }
-
             if ((ci != null) && (ci.contactIdOrZero > 0)) {
                 // Update usage information for the number associated with the contact ID.
                 // We need to use both the number and the ID for obtaining a data ID since other
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 227c9d4..85feac8 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8654,9 +8654,10 @@
                 "location_permissions_upgrade_to_q_mode";
 
         /**
-         * Comma separated list of enabled overlay packages for all android.theme.customization.*
-         * categories. If there is no corresponding package included for a category, then all
-         * overlay packages in that category must be disabled.
+         * Map of android.theme.customization.* categories to the enabled overlay package for that
+         * category, formatted as a serialized {@link org.json.JSONObject}. If there is no
+         * corresponding package included for a category, then all overlay packages in that
+         * category must be disabled.
          * @hide
          */
         @SystemApi
@@ -8664,7 +8665,7 @@
                 "theme_customization_overlay_packages";
 
         private static final Validator THEME_CUSTOMIZATION_OVERLAY_PACKAGES_VALIDATOR =
-                new SettingsValidators.PackageNameListValidator(",");
+                SettingsValidators.JSON_OBJECT_VALIDATOR;
 
         /**
          * Controls whether aware is enabled.
diff --git a/core/java/android/provider/SettingsValidators.java b/core/java/android/provider/SettingsValidators.java
index 25e77867..4051213 100644
--- a/core/java/android/provider/SettingsValidators.java
+++ b/core/java/android/provider/SettingsValidators.java
@@ -19,9 +19,13 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.net.Uri;
+import android.text.TextUtils;
 
 import com.android.internal.util.ArrayUtils;
 
+import org.json.JSONException;
+import org.json.JSONObject;
+
 import java.util.Locale;
 
 /**
@@ -162,6 +166,19 @@
         }
     };
 
+    /** {@link Validator} that checks whether a value is a valid {@link JSONObject}. */
+    public static final Validator JSON_OBJECT_VALIDATOR = (value) -> {
+        if (TextUtils.isEmpty(value)) {
+            return false;
+        }
+        try {
+            new JSONObject(value);
+            return true;
+        } catch (JSONException e) {
+            return false;
+        }
+    };
+
     public interface Validator {
         /**
          * Returns whether the input value is valid. Subclasses should handle the case where the
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 7f6423a..336c2e5 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -38,9 +38,11 @@
     public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid";
     public static final String SAFETY_HUB = "settings_safety_hub";
     public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
-    public static final String GLOBAL_ACTIONS_GRID_ENABLED = "settings_global_actions_grid_enabled";
+    public static final String FORCE_GLOBAL_ACTIONS_GRID_ENABLED =
+            "settings_global_actions_force_grid_enabled";
     public static final String GLOBAL_ACTIONS_PANEL_ENABLED =
             "settings_global_actions_panel_enabled";
+    public static final String DYNAMIC_SYSTEM = "settings_dynamic_system";
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
@@ -52,12 +54,12 @@
         DEFAULT_FLAGS.put("settings_slice_injection", "true");
         DEFAULT_FLAGS.put("settings_systemui_theme", "true");
         DEFAULT_FLAGS.put("settings_mainline_module", "true");
-        DEFAULT_FLAGS.put("settings_dynamic_system", "false");
+        DEFAULT_FLAGS.put(DYNAMIC_SYSTEM, "false");
         DEFAULT_FLAGS.put(SEAMLESS_TRANSFER, "false");
         DEFAULT_FLAGS.put(HEARING_AID_SETTINGS, "false");
         DEFAULT_FLAGS.put(SAFETY_HUB, "true");
         DEFAULT_FLAGS.put(SCREENRECORD_LONG_PRESS, "false");
-        DEFAULT_FLAGS.put(GLOBAL_ACTIONS_GRID_ENABLED, "true");
+        DEFAULT_FLAGS.put(FORCE_GLOBAL_ACTIONS_GRID_ENABLED, "false");
         DEFAULT_FLAGS.put(GLOBAL_ACTIONS_PANEL_ENABLED, "true");
         DEFAULT_FLAGS.put("settings_wifi_details_saved_screen", "true");
         DEFAULT_FLAGS.put("settings_wifi_details_datausage_header", "false");
diff --git a/core/java/android/util/HashedStringCache.java b/core/java/android/util/HashedStringCache.java
new file mode 100644
index 0000000..8ce8514
--- /dev/null
+++ b/core/java/android/util/HashedStringCache.java
@@ -0,0 +1,205 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+
+import java.io.File;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+/**
+ * HashedStringCache provides hashing functionality with an underlying LRUCache and expiring salt.
+ * Salt and expiration time are being stored under the tag passed in by the calling package --
+ * intended usage is the calling package name.
+ * TODO: Add unit tests b/129870147
+ * @hide
+ */
+public class HashedStringCache {
+    private static HashedStringCache sHashedStringCache = null;
+    private static final Charset UTF_8 = Charset.forName("UTF-8");
+    private static final int HASH_CACHE_SIZE = 100;
+    private static final int HASH_LENGTH = 8;
+    private static final String HASH_SALT = "_hash_salt";
+    private static final String HASH_SALT_DATE = "_hash_salt_date";
+    private static final String HASH_SALT_GEN = "_hash_salt_gen";
+    // For privacy we need to rotate the salt regularly
+    private static final long DAYS_TO_MILLIS = 1000 * 60 * 60 * 24;
+    private static final int MAX_SALT_DAYS = 100;
+    private final LruCache<String, String> mHashes;
+    private final SecureRandom mSecureRandom;
+    private final Object mPreferenceLock = new Object();
+    private final MessageDigest mDigester;
+    private byte[] mSalt;
+    private int mSaltGen;
+    private SharedPreferences mSharedPreferences;
+
+    private static final String TAG = "HashedStringCache";
+    private static final boolean DEBUG = false;
+
+    private HashedStringCache() {
+        mHashes = new LruCache<>(HASH_CACHE_SIZE);
+        mSecureRandom = new SecureRandom();
+        try {
+            mDigester = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException impossible) {
+            // this can't happen - MD5 is always present
+            throw new RuntimeException(impossible);
+        }
+    }
+
+    /**
+     * @return - instance of the HashedStringCache
+     * @hide
+     */
+    public static HashedStringCache getInstance() {
+        if (sHashedStringCache == null) {
+            sHashedStringCache = new HashedStringCache();
+        }
+        return sHashedStringCache;
+    }
+
+    /**
+     * Take the string and context and create a hash of the string. Trigger refresh on salt if salt
+     * is more than 7 days old
+     * @param context - callers context to retrieve SharedPreferences
+     * @param clearText - string that needs to be hashed
+     * @param tag - class name to use for storing values in shared preferences
+     * @param saltExpirationDays - number of days we may keep the same salt
+     *                           special value -1 will short-circuit and always return null.
+     * @return - HashResult containing the hashed string and the generation of the hash salt, null
+     *      if clearText string is empty
+     *
+     * @hide
+     */
+    public HashResult hashString(Context context, String tag, String clearText,
+            int saltExpirationDays) {
+        if (TextUtils.isEmpty(clearText) || saltExpirationDays == -1) {
+            return null;
+        }
+
+        populateSaltValues(context, tag, saltExpirationDays);
+        String hashText = mHashes.get(clearText);
+        if (hashText != null) {
+            return new HashResult(hashText, mSaltGen);
+        }
+
+        mDigester.reset();
+        mDigester.update(mSalt);
+        mDigester.update(clearText.getBytes(UTF_8));
+        byte[] bytes = mDigester.digest();
+        int len = Math.min(HASH_LENGTH, bytes.length);
+        hashText = Base64.encodeToString(bytes, 0, len, Base64.NO_PADDING | Base64.NO_WRAP);
+        mHashes.put(clearText, hashText);
+
+        return new HashResult(hashText, mSaltGen);
+    }
+
+    /**
+     * Populates the mSharedPreferences and checks if there is a salt present and if it's older than
+     * 7 days
+     * @param tag - class name to use for storing values in shared preferences
+     * @param saltExpirationDays - number of days we may keep the same salt
+     * @param saltDate - the date retrieved from configuration
+     * @return - true if no salt or salt is older than 7 days
+     */
+    private boolean checkNeedsNewSalt(String tag, int saltExpirationDays, long saltDate) {
+        if (saltDate == 0 || saltExpirationDays < -1) {
+            return true;
+        }
+        if (saltExpirationDays > MAX_SALT_DAYS) {
+            saltExpirationDays = MAX_SALT_DAYS;
+        }
+        long now = System.currentTimeMillis();
+        long delta = now - saltDate;
+        // Check for delta < 0 to make sure we catch if someone puts their phone far in the
+        // future and then goes back to normal time.
+        return delta >= saltExpirationDays * DAYS_TO_MILLIS || delta < 0;
+    }
+
+    /**
+     * Populate the salt and saltGen member variables if they aren't already set / need refreshing.
+     * @param context - to get sharedPreferences
+     * @param tag - class name to use for storing values in shared preferences
+     * @param saltExpirationDays - number of days we may keep the same salt
+     */
+    private void populateSaltValues(Context context, String tag, int saltExpirationDays) {
+        synchronized (mPreferenceLock) {
+            // check if we need to refresh the salt
+            mSharedPreferences = getHashSharedPreferences(context);
+            long saltDate = mSharedPreferences.getLong(tag + HASH_SALT_DATE, 0);
+            boolean needsNewSalt = checkNeedsNewSalt(tag, saltExpirationDays, saltDate);
+            if (needsNewSalt) {
+                mHashes.evictAll();
+            }
+            if (mSalt == null || needsNewSalt) {
+                String saltString = mSharedPreferences.getString(tag + HASH_SALT, null);
+                mSaltGen = mSharedPreferences.getInt(tag + HASH_SALT_GEN, 0);
+                if (saltString == null || needsNewSalt) {
+                    mSaltGen++;
+                    byte[] saltBytes = new byte[16];
+                    mSecureRandom.nextBytes(saltBytes);
+                    saltString = Base64.encodeToString(saltBytes,
+                            Base64.NO_PADDING | Base64.NO_WRAP);
+                    mSharedPreferences.edit()
+                            .putString(tag + HASH_SALT, saltString)
+                            .putInt(tag + HASH_SALT_GEN, mSaltGen)
+                            .putLong(tag + HASH_SALT_DATE, System.currentTimeMillis()).apply();
+                    if (DEBUG) {
+                        Log.d(TAG, "created a new salt: " + saltString);
+                    }
+                }
+                mSalt = saltString.getBytes(UTF_8);
+            }
+        }
+    }
+
+    /**
+     * Android:ui doesn't have persistent preferences, so need to fall back on this hack originally
+     * from ChooserActivity.java
+     * @param context
+     * @return
+     */
+    private SharedPreferences getHashSharedPreferences(Context context) {
+        final File prefsFile = new File(new File(
+                Environment.getDataUserCePackageDirectory(
+                        StorageManager.UUID_PRIVATE_INTERNAL,
+                        context.getUserId(), context.getPackageName()),
+                "shared_prefs"),
+                "hashed_cache.xml");
+        return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
+    }
+
+    /**
+     * Helper class to hold hashed string and salt generation.
+     */
+    public class HashResult {
+        public String hashedString;
+        public int saltGeneration;
+
+        public HashResult(String hString, int saltGen) {
+            hashedString = hString;
+            saltGeneration = saltGen;
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/CallIdentification.aidl b/core/java/android/view/IInputMonitorHost.aidl
similarity index 75%
rename from telecomm/java/android/telecom/CallIdentification.aidl
rename to core/java/android/view/IInputMonitorHost.aidl
index 532535c..bde737d 100644
--- a/telecomm/java/android/telecom/CallIdentification.aidl
+++ b/core/java/android/view/IInputMonitorHost.aidl
@@ -1,5 +1,5 @@
-/*
- * Copyright 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,9 +14,12 @@
  * limitations under the License.
  */
 
-package android.telecom;
+package android.view;
 
 /**
- * {@hide}
+ * @hide
  */
-parcelable CallIdentification;
+oneway interface IInputMonitorHost {
+    void pilferPointers();
+    void dispose();
+}
diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java
index af2b992..ecb727c 100644
--- a/core/java/android/view/InputChannel.java
+++ b/core/java/android/view/InputChannel.java
@@ -17,8 +17,8 @@
 package android.view;
 
 import android.annotation.UnsupportedAppUsage;
-import android.os.Parcel;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.Slog;
 
@@ -31,9 +31,9 @@
  */
 public final class InputChannel implements Parcelable {
     private static final String TAG = "InputChannel";
-    
+
     private static final boolean DEBUG = false;
-    
+
     @UnsupportedAppUsage
     public static final @android.annotation.NonNull Parcelable.Creator<InputChannel> CREATOR
             = new Parcelable.Creator<InputChannel>() {
@@ -42,12 +42,12 @@
             result.readFromParcel(source);
             return result;
         }
-        
+
         public InputChannel[] newArray(int size) {
             return new InputChannel[size];
         }
     };
-    
+
     @SuppressWarnings("unused")
     @UnsupportedAppUsage
     private long mPtr; // used by native code
@@ -81,7 +81,7 @@
             super.finalize();
         }
     }
-    
+
     /**
      * Creates a new input channel pair.  One channel should be provided to the input
      * dispatcher and the other to the application's input queue.
@@ -100,7 +100,7 @@
         }
         return nativeOpenInputChannelPair(name);
     }
-    
+
     /**
      * Gets the name of the input channel.
      * @return The input channel name.
@@ -118,7 +118,7 @@
     public void dispose() {
         nativeDispose(false);
     }
-    
+
     /**
      * Transfers ownership of the internal state of the input channel to another
      * instance and invalidates this instance.  This is used to pass an input channel
@@ -129,7 +129,7 @@
         if (outParameter == null) {
             throw new IllegalArgumentException("outParameter must not be null");
         }
-        
+
         nativeTransferTo(outParameter);
     }
 
@@ -151,7 +151,7 @@
         if (in == null) {
             throw new IllegalArgumentException("in must not be null");
         }
-        
+
         nativeReadFromParcel(in);
     }
 
@@ -160,7 +160,7 @@
         if (out == null) {
             throw new IllegalArgumentException("out must not be null");
         }
-        
+
         nativeWriteToParcel(out);
 
         if ((flags & PARCELABLE_WRITE_RETURN_VALUE) != 0) {
diff --git a/telecomm/java/android/telecom/CallIdentification.aidl b/core/java/android/view/InputMonitor.aidl
similarity index 73%
copy from telecomm/java/android/telecom/CallIdentification.aidl
copy to core/java/android/view/InputMonitor.aidl
index 532535c..bdd14fe 100644
--- a/telecomm/java/android/telecom/CallIdentification.aidl
+++ b/core/java/android/view/InputMonitor.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright 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.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-package android.telecom;
+package android.view;
 
-/**
- * {@hide}
- */
-parcelable CallIdentification;
+parcelable InputMonitor;
diff --git a/core/java/android/view/InputMonitor.java b/core/java/android/view/InputMonitor.java
new file mode 100644
index 0000000..693f287
--- /dev/null
+++ b/core/java/android/view/InputMonitor.java
@@ -0,0 +1,127 @@
+/*
+ * 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.view;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+/**
+ * @hide
+ */
+public final class InputMonitor implements Parcelable {
+    private static final String TAG = "InputMonitor";
+
+    private static final boolean DEBUG = false;
+
+    public static final Parcelable.Creator<InputMonitor> CREATOR =
+            new Parcelable.Creator<InputMonitor>() {
+
+            public InputMonitor createFromParcel(Parcel source) {
+                return new InputMonitor(source);
+            }
+
+            public InputMonitor[] newArray(int size) {
+                return new InputMonitor[size];
+            }
+    };
+
+    @NonNull
+    private final String mName;
+    @NonNull
+    private final InputChannel mChannel;
+    @NonNull
+    private final IInputMonitorHost mHost;
+
+    public InputMonitor(@NonNull String name, @NonNull InputChannel channel,
+            @NonNull IInputMonitorHost host) {
+        mName = name;
+        mChannel = channel;
+        mHost = host;
+    }
+
+    public InputMonitor(Parcel in) {
+        mName = in.readString();
+        mChannel = in.readParcelable(null);
+        mHost = IInputMonitorHost.Stub.asInterface(in.readStrongBinder());
+    }
+
+    /**
+     * Get the {@link InputChannel} corresponding to this InputMonitor
+     */
+    public InputChannel getInputChannel() {
+        return mChannel;
+    }
+
+    /**
+     * Get the name of this channel.
+     */
+    public String getName() {
+        return mName;
+    }
+
+
+    /**
+     * Takes all of the current pointer events streams that are currently being sent to this
+     * monitor and generates appropriate cancellations for the windows that would normally get
+     * them.
+     *
+     * This method should be used with caution as unexpected pilfering can break fundamental user
+     * interactions.
+     */
+    public void pilferPointers() {
+        try {
+            mHost.pilferPointers();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Disposes the input monitor.
+     *
+     * Explicitly release all of the resources this monitor is holding on to (e.g. the
+     * InputChannel). Once this method is called, this monitor and any resources it's provided may
+     * no longer be used.
+     */
+    public void dispose() {
+        mChannel.dispose();
+        try {
+            mHost.dispose();
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mName);
+        out.writeParcelable(mChannel, flags);
+        out.writeStrongBinder(mHost.asBinder());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "InputMonitor{mName=" + mName + ", mChannel=" + mChannel + ", mHost=" + mHost + "}";
+    }
+}
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 35cf129..f9b629c8 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -566,7 +566,7 @@
         String layout = res.getResourceEntryName(resource);
 
         try {
-            Class clazz = mPrecompiledClassLoader.loadClass("" + pkg + ".CompiledView");
+            Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
             Method inflater = clazz.getMethod(layout, Context.class, int.class);
             View view = (View) inflater.invoke(null, mContext, resource);
 
@@ -827,8 +827,8 @@
 
             if (constructor == null) {
                 // Class not found in the cache, see if it's real, and try to add it
-                clazz = mContext.getClassLoader().loadClass(
-                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
+                clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
+                        mContext.getClassLoader()).asSubclass(View.class);
 
                 if (mFilter != null && clazz != null) {
                     boolean allowed = mFilter.onLoadClass(clazz);
@@ -846,8 +846,8 @@
                     Boolean allowedState = mFilterMap.get(name);
                     if (allowedState == null) {
                         // New class -- remember whether it is allowed
-                        clazz = mContext.getClassLoader().loadClass(
-                                prefix != null ? (prefix + name) : name).asSubclass(View.class);
+                        clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
+                                mContext.getClassLoader()).asSubclass(View.class);
 
                         boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                         mFilterMap.put(name, allowed);
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index 7ab9534..04ac922 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -26,6 +26,7 @@
 import android.graphics.RenderNode;
 import android.os.Build;
 import android.os.Handler;
+import android.os.Looper;
 import android.util.SparseIntArray;
 
 import com.android.internal.util.VirtualRefBasePtr;
@@ -191,6 +192,9 @@
         }
 
         mState = STATE_DELAYED;
+        if (mHandler == null) {
+            mHandler = new Handler(true);
+        }
         applyInterpolator();
 
         if (mNativePtr == null) {
@@ -224,9 +228,6 @@
     private void moveToRunningState() {
         mState = STATE_RUNNING;
         if (mNativePtr != null) {
-            if (mHandler == null) {
-                mHandler = new Handler();
-            }
             nStart(mNativePtr.get());
         }
         notifyStartListeners();
@@ -503,7 +504,11 @@
     // Called by native
     @UnsupportedAppUsage
     private static void callOnFinished(RenderNodeAnimator animator) {
-        animator.mHandler.post(animator::onFinished);
+        if (animator.mHandler != null) {
+            animator.mHandler.post(animator::onFinished);
+        } else {
+            new Handler(Looper.getMainLooper(), null, true).post(animator::onFinished);
+        }
     }
 
     @Override
diff --git a/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java b/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java
index b034846..22e374f2 100644
--- a/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java
+++ b/core/java/android/view/textclassifier/intent/ClassificationIntentFactory.java
@@ -48,6 +48,7 @@
                 context.getString(com.android.internal.R.string.translate),
                 /* titleWithEntity */ null,
                 context.getString(com.android.internal.R.string.translate_desc),
+                /* descriptionWithAppName */ null,
                 new Intent(Intent.ACTION_TRANSLATE)
                         // TODO: Probably better to introduce a "translate" scheme instead of
                         // using EXTRA_TEXT.
diff --git a/core/java/android/view/textclassifier/intent/LabeledIntent.java b/core/java/android/view/textclassifier/intent/LabeledIntent.java
index 11d64f1..b4bc8d3 100644
--- a/core/java/android/view/textclassifier/intent/LabeledIntent.java
+++ b/core/java/android/view/textclassifier/intent/LabeledIntent.java
@@ -56,6 +56,8 @@
     @Nullable
     public final String titleWithEntity;
     public final String description;
+    @Nullable
+    public final String descriptionWithAppName;
     // Do not update this intent.
     public final Intent intent;
     public final int requestCode;
@@ -75,6 +77,7 @@
             @Nullable String titleWithoutEntity,
             @Nullable String titleWithEntity,
             String description,
+            @Nullable String descriptionWithAppName,
             Intent intent,
             int requestCode) {
         if (TextUtils.isEmpty(titleWithEntity) && TextUtils.isEmpty(titleWithoutEntity)) {
@@ -84,6 +87,7 @@
         this.titleWithoutEntity = titleWithoutEntity;
         this.titleWithEntity = titleWithEntity;
         this.description = Preconditions.checkNotNull(description);
+        this.descriptionWithAppName = descriptionWithAppName;
         this.intent = Preconditions.checkNotNull(intent);
         this.requestCode = requestCode;
     }
@@ -141,11 +145,39 @@
             Log.w(TAG, "Custom titleChooser return null, fallback to the default titleChooser");
             title = DEFAULT_TITLE_CHOOSER.chooseTitle(this, resolveInfo);
         }
-        final RemoteAction action = new RemoteAction(icon, title, description, pendingIntent);
+        final RemoteAction action =
+                new RemoteAction(icon, title, resolveDescription(resolveInfo, pm), pendingIntent);
         action.setShouldShowIcon(shouldShowIcon);
         return new Result(resolvedIntent, action);
     }
 
+    private String resolveDescription(ResolveInfo resolveInfo, PackageManager packageManager) {
+        if (!TextUtils.isEmpty(descriptionWithAppName)) {
+            // Example string format of descriptionWithAppName: "Use %1$s to open map".
+            String applicationName = getApplicationName(resolveInfo, packageManager);
+            if (!TextUtils.isEmpty(applicationName)) {
+                return String.format(descriptionWithAppName, applicationName);
+            }
+        }
+        return description;
+    }
+
+    @Nullable
+    private String getApplicationName(
+            ResolveInfo resolveInfo, PackageManager packageManager) {
+        if (resolveInfo.activityInfo == null) {
+            return null;
+        }
+        if ("android".equals(resolveInfo.activityInfo.packageName)) {
+            return null;
+        }
+        if (resolveInfo.activityInfo.applicationInfo == null) {
+            return null;
+        }
+        return (String) packageManager.getApplicationLabel(
+                resolveInfo.activityInfo.applicationInfo);
+    }
+
     private Bundle getFromTextClassifierExtra(@Nullable Bundle textLanguagesBundle) {
         if (textLanguagesBundle != null) {
             final Bundle bundle = new Bundle();
diff --git a/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java b/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java
index 7916791..8d60ad8 100644
--- a/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java
+++ b/core/java/android/view/textclassifier/intent/LegacyClassificationIntentFactory.java
@@ -46,6 +46,7 @@
  * Creates intents based on the classification type.
  * @hide
  */
+// TODO: Consider to support {@code descriptionWithAppName}.
 public final class LegacyClassificationIntentFactory implements ClassificationIntentFactory {
 
     private static final String TAG = "LegacyClassificationIntentFactory";
@@ -108,6 +109,7 @@
                 context.getString(com.android.internal.R.string.email),
                 /* titleWithEntity */ null,
                 context.getString(com.android.internal.R.string.email_desc),
+                /* descriptionWithAppName */ null,
                 new Intent(Intent.ACTION_SENDTO)
                         .setData(Uri.parse(String.format("mailto:%s", text))),
                 LabeledIntent.DEFAULT_REQUEST_CODE));
@@ -115,6 +117,7 @@
                 context.getString(com.android.internal.R.string.add_contact),
                 /* titleWithEntity */ null,
                 context.getString(com.android.internal.R.string.add_contact_desc),
+                /* descriptionWithAppName */ null,
                 new Intent(Intent.ACTION_INSERT_OR_EDIT)
                         .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
                         .putExtra(ContactsContract.Intents.Insert.EMAIL, text),
@@ -133,6 +136,7 @@
                     context.getString(com.android.internal.R.string.dial),
                     /* titleWithEntity */ null,
                     context.getString(com.android.internal.R.string.dial_desc),
+                    /* descriptionWithAppName */ null,
                     new Intent(Intent.ACTION_DIAL).setData(
                             Uri.parse(String.format("tel:%s", text))),
                     LabeledIntent.DEFAULT_REQUEST_CODE));
@@ -141,6 +145,7 @@
                 context.getString(com.android.internal.R.string.add_contact),
                 /* titleWithEntity */ null,
                 context.getString(com.android.internal.R.string.add_contact_desc),
+                /* descriptionWithAppName */ null,
                 new Intent(Intent.ACTION_INSERT_OR_EDIT)
                         .setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE)
                         .putExtra(ContactsContract.Intents.Insert.PHONE, text),
@@ -150,6 +155,7 @@
                     context.getString(com.android.internal.R.string.sms),
                     /* titleWithEntity */ null,
                     context.getString(com.android.internal.R.string.sms_desc),
+                    /* descriptionWithAppName */ null,
                     new Intent(Intent.ACTION_SENDTO)
                             .setData(Uri.parse(String.format("smsto:%s", text))),
                     LabeledIntent.DEFAULT_REQUEST_CODE));
@@ -166,6 +172,7 @@
                     context.getString(com.android.internal.R.string.map),
                     /* titleWithEntity */ null,
                     context.getString(com.android.internal.R.string.map_desc),
+                    /* descriptionWithAppName */ null,
                     new Intent(Intent.ACTION_VIEW)
                             .setData(Uri.parse(String.format("geo:0,0?q=%s", encText))),
                     LabeledIntent.DEFAULT_REQUEST_CODE));
@@ -185,6 +192,7 @@
                 context.getString(com.android.internal.R.string.browse),
                 /* titleWithEntity */ null,
                 context.getString(com.android.internal.R.string.browse_desc),
+                /* descriptionWithAppName */ null,
                 new Intent(Intent.ACTION_VIEW)
                         .setDataAndNormalize(Uri.parse(text))
                         .putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()),
@@ -216,6 +224,7 @@
                 context.getString(com.android.internal.R.string.view_flight),
                 /* titleWithEntity */ null,
                 context.getString(com.android.internal.R.string.view_flight_desc),
+                /* descriptionWithAppName */ null,
                 new Intent(Intent.ACTION_WEB_SEARCH)
                         .putExtra(SearchManager.QUERY, text),
                 text.hashCode()));
@@ -231,6 +240,7 @@
                 context.getString(com.android.internal.R.string.view_calendar),
                 /* titleWithEntity */ null,
                 context.getString(com.android.internal.R.string.view_calendar_desc),
+                /* descriptionWithAppName */ null,
                 new Intent(Intent.ACTION_VIEW).setData(builder.build()),
                 LabeledIntent.DEFAULT_REQUEST_CODE);
     }
@@ -243,6 +253,7 @@
                 context.getString(com.android.internal.R.string.add_calendar_event),
                 /* titleWithEntity */ null,
                 context.getString(com.android.internal.R.string.add_calendar_event_desc),
+                /* descriptionWithAppName */ null,
                 new Intent(Intent.ACTION_INSERT)
                         .setData(CalendarContract.Events.CONTENT_URI)
                         .putExtra(CalendarContract.EXTRA_EVENT_ALL_DAY, isAllDay)
@@ -260,6 +271,7 @@
                 context.getString(com.android.internal.R.string.define),
                 /* titleWithEntity */ null,
                 context.getString(com.android.internal.R.string.define_desc),
+                /* descriptionWithAppName */ null,
                 new Intent(Intent.ACTION_DEFINE)
                         .putExtra(Intent.EXTRA_TEXT, text),
                 text.hashCode()));
diff --git a/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java b/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java
index 59cd7ab..7a39569 100644
--- a/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java
+++ b/core/java/android/view/textclassifier/intent/TemplateIntentFactory.java
@@ -61,6 +61,7 @@
                             remoteActionTemplate.titleWithoutEntity,
                             remoteActionTemplate.titleWithEntity,
                             remoteActionTemplate.description,
+                            remoteActionTemplate.descriptionWithAppName,
                             createIntent(remoteActionTemplate),
                             remoteActionTemplate.requestCode == null
                                     ? LabeledIntent.DEFAULT_REQUEST_CODE
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index a1d0cdc..d553c6c 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -74,6 +74,7 @@
 import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.provider.DocumentsContract;
 import android.provider.Downloads;
 import android.provider.OpenableColumns;
@@ -83,6 +84,7 @@
 import android.service.chooser.IChooserTargetService;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.HashedStringCache;
 import android.util.Log;
 import android.util.Size;
 import android.util.Slog;
@@ -106,6 +108,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.ImageUtils;
@@ -170,6 +173,11 @@
     private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
     private static final int WATCHDOG_TIMEOUT_MILLIS = 3000;
 
+    private static final int DEFAULT_SALT_EXPIRATION_DAYS = 7;
+    private int mMaxHashSaltDays = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
+            SystemUiDeviceConfigFlags.HASH_SALT_MAX_DAYS,
+            DEFAULT_SALT_EXPIRATION_DAYS);
+
     private Bundle mReplacementExtras;
     private IntentSender mChosenComponentSender;
     private IntentSender mRefinementIntentSender;
@@ -201,7 +209,8 @@
     private static final int SHORTCUT_MANAGER_SHARE_TARGET_RESULT_COMPLETED = 4;
     private static final int LIST_VIEW_UPDATE_MESSAGE = 5;
 
-    private static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
+    @VisibleForTesting
+    public static final int LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS = 250;
 
     private boolean mListViewDataChanged = false;
 
@@ -991,6 +1000,7 @@
             // Lower values mean the ranking was better.
             int cat = 0;
             int value = which;
+            HashedStringCache.HashResult directTargetHashed = null;
             switch (mChooserListAdapter.getPositionTargetType(which)) {
                 case ChooserListAdapter.TARGET_CALLER:
                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
@@ -998,6 +1008,17 @@
                     break;
                 case ChooserListAdapter.TARGET_SERVICE:
                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET;
+                    value -= mChooserListAdapter.getCallerTargetCount();
+                    // Log the package name + target name to answer the question if most users
+                    // share to mostly the same person or to a bunch of different people.
+                    ChooserTarget target =
+                            mChooserListAdapter.mServiceTargets.get(value).getChooserTarget();
+                    directTargetHashed = HashedStringCache.getInstance().hashString(
+                            this,
+                            TAG,
+                            target.getComponentName().getPackageName()
+                                    + target.getTitle().toString(),
+                            mMaxHashSaltDays);
                     break;
                 case ChooserListAdapter.TARGET_STANDARD:
                     cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
@@ -1007,6 +1028,15 @@
             }
 
             if (cat != 0) {
+                LogMaker targetLogMaker = new LogMaker(cat).setSubtype(value);
+                if (directTargetHashed != null) {
+                    targetLogMaker.addTaggedData(
+                            MetricsEvent.FIELD_HASHED_TARGET_NAME, directTargetHashed.hashedString);
+                    targetLogMaker.addTaggedData(
+                                    MetricsEvent.FIELD_HASHED_TARGET_SALT_GEN,
+                                    directTargetHashed.saltGeneration);
+                }
+                getMetricsLogger().write(targetLogMaker);
                 MetricsLogger.action(this, cat, value);
             }
 
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 1c90182..72dbbf3 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -26,8 +26,9 @@
 import com.android.internal.app.IAppOpsNotedCallback;
 
 interface IAppOpsService {
-    // These first methods are also called by native code, so must
+    // These methods are also called by native code, so must
     // be kept in sync with frameworks/native/libs/binder/include/binder/IAppOpsService.h
+    // and not be reordered
     int checkOperation(int code, int uid, String packageName);
     int noteOperation(int code, int uid, String packageName);
     int startOperation(IBinder token, int code, int uid, String packageName,
@@ -38,6 +39,10 @@
     void stopWatchingMode(IAppOpsCallback callback);
     IBinder getToken(IBinder clientToken);
     int permissionToOpCode(String permission);
+    int checkAudioOperation(int code, int usage, int uid, String packageName);
+    // End of methods also called by native code.
+    // Any new method exposed to native must be added after the last one, do not reorder
+
     int noteProxyOperation(int code, int proxyUid, String proxyPackageName,
                 int callingUid, String callingPackageName);
 
@@ -62,7 +67,6 @@
     void setMode(int code, int uid, String packageName, int mode);
     @UnsupportedAppUsage
     void resetAllModes(int reqUserId, String reqPackageName);
-    int checkAudioOperation(int code, int usage, int uid, String packageName);
     void setAudioRestriction(int code, int usage, int uid, int mode, in String[] exceptionPackages);
 
     void setUserRestrictions(in Bundle restrictions, IBinder token, int userHandle);
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 0e4897f..b26efc0 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -79,6 +79,7 @@
         STATE_TOP,                      // ActivityManager.PROCESS_STATE_TOP
         STATE_IMPORTANT_FOREGROUND,     // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION
         STATE_IMPORTANT_FOREGROUND,     // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+        STATE_IMPORTANT_FOREGROUND,     // ActivityManager.PROCESS_STATE_BOUND_TOP
         STATE_IMPORTANT_FOREGROUND,     // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         STATE_IMPORTANT_FOREGROUND,     // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         STATE_IMPORTANT_BACKGROUND,     // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index fd74c04..495a5fb 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -95,5 +95,10 @@
     public static final String COMPACT_MEDIA_SEEKBAR_ENABLED =
             "compact_media_notification_seekbar_enabled";
 
+    /**
+     * (int) Maximum number of days to retain the salt for hashing direct share targets in logging
+     */
+    public static final String HASH_SALT_MAX_DAYS = "hash_salt_max_days";
+
     private SystemUiDeviceConfigFlags() { }
 }
diff --git a/core/java/com/android/internal/os/KernelCpuThreadReader.java b/core/java/com/android/internal/os/KernelCpuThreadReader.java
index 3686048..d92f725b 100644
--- a/core/java/com/android/internal/os/KernelCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelCpuThreadReader.java
@@ -21,6 +21,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 
 import java.io.IOException;
@@ -29,6 +30,7 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.function.Predicate;
 
 /**
@@ -233,7 +235,7 @@
         mFrequencyBucketCreator =
                 new FrequencyBucketCreator(mProcTimeInStateReader.getFrequenciesKhz(), numBuckets);
         mFrequenciesKhz =
-                mFrequencyBucketCreator.getBucketMinFrequencies(
+                mFrequencyBucketCreator.bucketFrequencies(
                         mProcTimeInStateReader.getFrequenciesKhz());
     }
 
@@ -317,7 +319,7 @@
         if (cpuUsagesLong == null) {
             return null;
         }
-        int[] cpuUsages = mFrequencyBucketCreator.getBucketedValues(cpuUsagesLong);
+        int[] cpuUsages = mFrequencyBucketCreator.bucketValues(cpuUsagesLong);
 
         return new ThreadCpuUsage(threadId, threadName, cpuUsages);
     }
@@ -359,89 +361,46 @@
         }
     }
 
-    /** Puts frequencies and usage times into buckets */
+    /**
+     * Quantizes a list of N frequencies into a list of M frequencies (where M<=N)
+     *
+     * <p>In order to reduce data sent from the device, we discard precise frequency information for
+     * an approximation. This is done by putting groups of adjacent frequencies into the same
+     * bucket, and then reporting that bucket under the minimum frequency in that bucket.
+     *
+     * <p>Many devices have multiple core clusters. We do not want to report frequencies from
+     * different clusters under the same bucket, so some complication arises.
+     *
+     * <p>Buckets are allocated evenly across all core clusters, i.e. they all have the same number
+     * of buckets regardless of how many frequencies they contain. This is done to reduce code
+     * complexity, and in practice the number of frequencies doesn't vary too much between core
+     * clusters.
+     *
+     * <p>If the number of buckets is not a factor of the number of frequencies, the remainder of
+     * the frequencies are placed into the last bucket.
+     *
+     * <p>It is possible to have less buckets than asked for, so any calling code can't assume that
+     * initializing with N buckets will use return N values. This happens in two scenarios:
+     *
+     * <ul>
+     *   <li>There are less frequencies available than buckets asked for.
+     *   <li>There are less frequencies in a core cluster than buckets allocated to that core
+     *       cluster.
+     * </ul>
+     */
     @VisibleForTesting
     public static class FrequencyBucketCreator {
-        private final int mNumBuckets;
         private final int mNumFrequencies;
-        private final int mBigFrequenciesStartIndex;
-        private final int mLittleNumBuckets;
-        private final int mBigNumBuckets;
-        private final int mLittleBucketSize;
-        private final int mBigBucketSize;
+        private final int mNumBuckets;
+        private final int[] mBucketStartIndices;
 
-        /**
-         * Buckets based of a list of frequencies
-         *
-         * @param frequencies the frequencies to base buckets off
-         * @param numBuckets how many buckets to create
-         */
         @VisibleForTesting
-        public FrequencyBucketCreator(long[] frequencies, int numBuckets) {
-            Preconditions.checkArgument(numBuckets > 0);
-
+        public FrequencyBucketCreator(long[] frequencies, int targetNumBuckets) {
             mNumFrequencies = frequencies.length;
-            mBigFrequenciesStartIndex = getBigFrequenciesStartIndex(frequencies);
-
-            final int littleNumBuckets;
-            final int bigNumBuckets;
-            if (mBigFrequenciesStartIndex < frequencies.length) {
-                littleNumBuckets = numBuckets / 2;
-                bigNumBuckets = numBuckets - littleNumBuckets;
-            } else {
-                // If we've got no big frequencies, set all buckets to little frequencies
-                littleNumBuckets = numBuckets;
-                bigNumBuckets = 0;
-            }
-
-            // Ensure that we don't have more buckets than frequencies
-            mLittleNumBuckets = Math.min(littleNumBuckets, mBigFrequenciesStartIndex);
-            mBigNumBuckets =
-                    Math.min(bigNumBuckets, frequencies.length - mBigFrequenciesStartIndex);
-            mNumBuckets = mLittleNumBuckets + mBigNumBuckets;
-
-            // Set the size of each little and big bucket. If they have no buckets, the size is zero
-            mLittleBucketSize =
-                    mLittleNumBuckets == 0 ? 0 : mBigFrequenciesStartIndex / mLittleNumBuckets;
-            mBigBucketSize =
-                    mBigNumBuckets == 0
-                            ? 0
-                            : (frequencies.length - mBigFrequenciesStartIndex) / mBigNumBuckets;
-        }
-
-        /** Find the index where frequencies change from little core to big core */
-        @VisibleForTesting
-        public static int getBigFrequenciesStartIndex(long[] frequenciesKhz) {
-            for (int i = 0; i < frequenciesKhz.length - 1; i++) {
-                if (frequenciesKhz[i] > frequenciesKhz[i + 1]) {
-                    return i + 1;
-                }
-            }
-
-            return frequenciesKhz.length;
-        }
-
-        /** Get the minimum frequency in each bucket */
-        @VisibleForTesting
-        public int[] getBucketMinFrequencies(long[] frequenciesKhz) {
-            Preconditions.checkArgument(frequenciesKhz.length == mNumFrequencies);
-            // If there's only one bucket, we bucket everything together so the first bucket is the
-            // min frequency
-            if (mNumBuckets == 1) {
-                return new int[] {(int) frequenciesKhz[0]};
-            }
-
-            final int[] bucketMinFrequencies = new int[mNumBuckets];
-            // Initialize little buckets min frequencies
-            for (int i = 0; i < mLittleNumBuckets; i++) {
-                bucketMinFrequencies[i] = (int) frequenciesKhz[i * mLittleBucketSize];
-            }
-            // Initialize big buckets min frequencies
-            for (int i = 0; i < mBigNumBuckets; i++) {
-                final int frequencyIndex = mBigFrequenciesStartIndex + i * mBigBucketSize;
-                bucketMinFrequencies[mLittleNumBuckets + i] = (int) frequenciesKhz[frequencyIndex];
-            }
-            return bucketMinFrequencies;
+            int[] clusterStartIndices = getClusterStartIndices(frequencies);
+            mBucketStartIndices =
+                    getBucketStartIndices(clusterStartIndices, targetNumBuckets, mNumFrequencies);
+            mNumBuckets = mBucketStartIndices.length;
         }
 
         /**
@@ -453,34 +412,105 @@
          * @return the bucketed usage times
          */
         @VisibleForTesting
-        @SuppressWarnings("ForLoopReplaceableByForEach")
-        public int[] getBucketedValues(long[] values) {
+        public int[] bucketValues(long[] values) {
             Preconditions.checkArgument(values.length == mNumFrequencies);
-            final int[] bucketed = new int[mNumBuckets];
-
-            // If there's only one bucket, add all frequencies in
-            if (mNumBuckets == 1) {
-                for (int i = 0; i < values.length; i++) {
-                    bucketed[0] += values[i];
+            int[] buckets = new int[mNumBuckets];
+            for (int bucketIdx = 0; bucketIdx < mNumBuckets; bucketIdx++) {
+                final int bucketStartIdx = getLowerBound(bucketIdx, mBucketStartIndices);
+                final int bucketEndIdx =
+                        getUpperBound(bucketIdx, mBucketStartIndices, values.length);
+                for (int valuesIdx = bucketStartIdx; valuesIdx < bucketEndIdx; valuesIdx++) {
+                    buckets[bucketIdx] += values[valuesIdx];
                 }
-                return bucketed;
+            }
+            return buckets;
+        }
+
+        /** Get the minimum frequency in each bucket */
+        @VisibleForTesting
+        public int[] bucketFrequencies(long[] frequencies) {
+            Preconditions.checkArgument(frequencies.length == mNumFrequencies);
+            int[] buckets = new int[mNumBuckets];
+            for (int i = 0; i < buckets.length; i++) {
+                buckets[i] = (int) frequencies[mBucketStartIndices[i]];
+            }
+            return buckets;
+        }
+
+        /**
+         * Get the index in frequencies where each core cluster starts
+         *
+         * <p>The frequencies for each cluster are given in ascending order, appended to each other.
+         * This means that every time there is a decrease in frequencies (instead of increase) a new
+         * cluster has started.
+         */
+        private static int[] getClusterStartIndices(long[] frequencies) {
+            ArrayList<Integer> indices = new ArrayList<>();
+            indices.add(0);
+            for (int i = 0; i < frequencies.length - 1; i++) {
+                if (frequencies[i] >= frequencies[i + 1]) {
+                    indices.add(i + 1);
+                }
+            }
+            return ArrayUtils.convertToIntArray(indices);
+        }
+
+        /** Get the index in frequencies where each bucket starts */
+        private static int[] getBucketStartIndices(
+                int[] clusterStartIndices, int targetNumBuckets, int numFrequencies) {
+            int numClusters = clusterStartIndices.length;
+
+            // If we haven't got enough buckets for every cluster, we instead have one bucket per
+            // cluster, with the last bucket containing the remaining clusters
+            if (numClusters > targetNumBuckets) {
+                return Arrays.copyOfRange(clusterStartIndices, 0, targetNumBuckets);
             }
 
-            // Initialize the little buckets
-            for (int i = 0; i < mBigFrequenciesStartIndex; i++) {
-                final int bucketIndex = Math.min(i / mLittleBucketSize, mLittleNumBuckets - 1);
-                bucketed[bucketIndex] += values[i];
+            ArrayList<Integer> bucketStartIndices = new ArrayList<>();
+            for (int clusterIdx = 0; clusterIdx < numClusters; clusterIdx++) {
+                final int clusterStartIdx = getLowerBound(clusterIdx, clusterStartIndices);
+                final int clusterEndIdx =
+                        getUpperBound(clusterIdx, clusterStartIndices, numFrequencies);
+
+                final int numBucketsInCluster;
+                if (clusterIdx != numClusters - 1) {
+                    numBucketsInCluster = targetNumBuckets / numClusters;
+                } else {
+                    // If we're in the last cluster, the bucket will contain the remainder of the
+                    // frequencies
+                    int previousBucketsInCluster = targetNumBuckets / numClusters;
+                    numBucketsInCluster =
+                            targetNumBuckets - (previousBucketsInCluster * (numClusters - 1));
+                }
+
+                final int numFrequenciesInCluster = clusterEndIdx - clusterStartIdx;
+                // If there are less frequencies than buckets in a cluster, we have one bucket per
+                // frequency, and do not use the remaining buckets
+                final int numFrequenciesInBucket =
+                        Math.max(1, numFrequenciesInCluster / numBucketsInCluster);
+                for (int bucketIdx = 0; bucketIdx < numBucketsInCluster; bucketIdx++) {
+                    int bucketStartIdx = clusterStartIdx + bucketIdx * numFrequenciesInBucket;
+                    // If we've gone over the end index, ignore the rest of the buckets for this
+                    // cluster
+                    if (bucketStartIdx >= clusterEndIdx) {
+                        break;
+                    }
+                    bucketStartIndices.add(bucketStartIdx);
+                }
             }
-            // Initialize the big buckets
-            for (int i = mBigFrequenciesStartIndex; i < values.length; i++) {
-                final int bucketIndex =
-                        Math.min(
-                                mLittleNumBuckets
-                                        + (i - mBigFrequenciesStartIndex) / mBigBucketSize,
-                                mNumBuckets - 1);
-                bucketed[bucketIndex] += values[i];
+            return ArrayUtils.convertToIntArray(bucketStartIndices);
+        }
+
+        private static int getLowerBound(int index, int[] startIndices) {
+            return startIndices[index];
+        }
+
+        private static int getUpperBound(int index, int[] startIndices, int max) {
+            if (index != startIndices.length - 1) {
+                return startIndices[index + 1];
+            } else {
+                return max;
             }
-            return bucketed;
         }
     }
 
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 8ca0bd1..afdeb1b 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -197,9 +197,6 @@
 
     private Zygote() {}
 
-    /** Called for some security initialization before any fork. */
-    static native void nativeSecurityInit();
-
     /**
      * Forks a new VM instance.  The current VM must have been started
      * with the -Xzygote flag. <b>NOTE: new instance keeps all
@@ -384,22 +381,19 @@
     native protected static void nativeInstallSeccompUidGidFilter(int uidGidMin, int uidGidMax);
 
     /**
-     * Zygote unmount storage space on initializing.
-     * This method is called once.
-     */
-    protected static native void nativeUnmountStorageOnInit();
-
-    /**
-     * Get socket file descriptors (opened by init) from the environment and
-     * store them for access from native code later.
+     * Initialize the native state of the Zygote.  This inclues
+     *   - Fetching socket FDs from the environment
+     *   - Initializing security properties
+     *   - Unmounting storage as appropriate
+     *   - Loading necessary performance profile information
      *
      * @param isPrimary  True if this is the zygote process, false if it is zygote_secondary
      */
-    public static void getSocketFDs(boolean isPrimary) {
-        nativeGetSocketFDs(isPrimary);
+    static void initNativeState(boolean isPrimary) {
+        nativeInitNativeState(isPrimary);
     }
 
-    protected static native void nativeGetSocketFDs(boolean isPrimary);
+    protected static native void nativeInitNativeState(boolean isPrimary);
 
     /**
      * Returns the raw string value of a system property.
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 7cca7b7..bb7b09a 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -20,10 +20,10 @@
 import static android.system.OsConstants.S_IRWXO;
 
 import android.annotation.UnsupportedAppUsage;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.app.ApplicationLoaders;
 import android.content.pm.SharedLibraryInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.os.Build;
 import android.os.Environment;
 import android.os.IInstalld;
@@ -863,8 +863,6 @@
                 throw new RuntimeException("No ABI list supplied.");
             }
 
-            Zygote.getSocketFDs(isPrimaryZygote);
-
             // In some configurations, we avoid preloading resources and classes eagerly.
             // In such cases, we will preload things prior to our first fork.
             if (!enableLazyPreload) {
@@ -889,10 +887,8 @@
             // Zygote.
             Trace.setTracingEnabled(false, 0);
 
-            Zygote.nativeSecurityInit();
 
-            // Zygote process unmounts root storage spaces.
-            Zygote.nativeUnmountStorageOnInit();
+            Zygote.initNativeState(isPrimaryZygote);
 
             ZygoteHooks.stopZygoteNoThreadCreation();
 
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index ca5db94..625814d 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1143,7 +1143,6 @@
         // If we didn't request fullscreen layout, but we still got it because of the
         // mForceWindowDrawsStatusBarBackground flag, also consume top inset.
         boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
-                && (sysUiVisibility & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
                 && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
                 && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
                 && mForceWindowDrawsStatusBarBackground
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 000c044..0e31ab9 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -204,7 +204,6 @@
         "android_content_res_Configuration.cpp",
         "android_animation_PropertyValuesHolder.cpp",
         "android_security_Scrypt.cpp",
-        "com_android_internal_net_NetworkStatsFactory.cpp",
         "com_android_internal_os_AtomicDirectory.cpp",
         "com_android_internal_os_ClassLoaderFactory.cpp",
         "com_android_internal_os_FuseAppLoop.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index ccd0b66..967abce 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -227,7 +227,6 @@
 extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
 extern int register_android_security_Scrypt(JNIEnv *env);
 extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
-extern int register_com_android_internal_net_NetworkStatsFactory(JNIEnv *env);
 extern int register_com_android_internal_os_AtomicDirectory(JNIEnv *env);
 extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
@@ -1569,7 +1568,6 @@
     REG_JNI(register_android_animation_PropertyValuesHolder),
     REG_JNI(register_android_security_Scrypt),
     REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
-    REG_JNI(register_com_android_internal_net_NetworkStatsFactory),
     REG_JNI(register_com_android_internal_os_AtomicDirectory),
     REG_JNI(register_com_android_internal_os_FuseAppLoop),
 };
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index 86cda44..5e4d6e3 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -92,7 +92,9 @@
     CursorWindow* window;
     status_t status = CursorWindow::create(name, cursorWindowSize, &window);
     if (status || !window) {
-        ALOGE("Could not allocate CursorWindow '%s' of size %d due to error %d.",
+        jniThrowExceptionFmt(env,
+                "android/database/CursorWindowAllocationException",
+                "Could not allocate CursorWindow '%s' of size %d due to error %d.",
                 name.string(), cursorWindowSize, status);
         return 0;
     }
@@ -107,7 +109,9 @@
     CursorWindow* window;
     status_t status = CursorWindow::createFromParcel(parcel, &window);
     if (status || !window) {
-        ALOGE("Could not create CursorWindow from Parcel due to error %d, process fd count=%d",
+        jniThrowExceptionFmt(env,
+                "android/database/CursorWindowAllocationException",
+                "Could not create CursorWindow from Parcel due to error %d, process fd count=%d",
                 status, getFdCount());
         return 0;
     }
diff --git a/core/jni/android_hardware_SoundTrigger.cpp b/core/jni/android_hardware_SoundTrigger.cpp
index c7805ea..03057dc 100644
--- a/core/jni/android_hardware_SoundTrigger.cpp
+++ b/core/jni/android_hardware_SoundTrigger.cpp
@@ -21,6 +21,7 @@
 
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
 #include "core_jni_helpers.h"
 #include <system/sound_trigger.h>
 #include <soundtrigger/SoundTriggerCallback.h>
@@ -395,7 +396,7 @@
 
 static jint
 android_hardware_SoundTrigger_listModules(JNIEnv *env, jobject clazz,
-                                          jobject jModules)
+                                          jstring opPackageName, jobject jModules)
 {
     ALOGV("listModules");
 
@@ -411,7 +412,10 @@
     unsigned int numModules = 0;
     struct sound_trigger_module_descriptor *nModules = NULL;
 
-    status_t status = SoundTrigger::listModules(nModules, &numModules);
+    ScopedUtfChars opPackageNameStr(env, opPackageName);
+    const String16 opPackageNameString16 = String16(opPackageNameStr.c_str());
+
+    status_t status = SoundTrigger::listModules(opPackageNameString16, nModules, &numModules);
     if (status != NO_ERROR || numModules == 0) {
         return (jint)status;
     }
@@ -419,7 +423,7 @@
     nModules = (struct sound_trigger_module_descriptor *)
                             calloc(numModules, sizeof(struct sound_trigger_module_descriptor));
 
-    status = SoundTrigger::listModules(nModules, &numModules);
+    status = SoundTrigger::listModules(opPackageNameString16, nModules, &numModules);
     ALOGV("listModules SoundTrigger::listModules status %d numModules %d", status, numModules);
 
     if (status != NO_ERROR) {
@@ -470,16 +474,20 @@
 }
 
 static void
-android_hardware_SoundTrigger_setup(JNIEnv *env, jobject thiz, jobject weak_this)
+android_hardware_SoundTrigger_setup(JNIEnv *env, jobject thiz,
+                                    jstring opPackageName, jobject weak_this)
 {
     ALOGV("setup");
 
+    ScopedUtfChars opPackageNameStr(env, opPackageName);
+    const String16 opPackageNameString16 = String16(opPackageNameStr.c_str());
+
     sp<JNISoundTriggerCallback> callback = new JNISoundTriggerCallback(env, thiz, weak_this);
 
     sound_trigger_module_handle_t handle =
             (sound_trigger_module_handle_t)env->GetIntField(thiz, gModuleFields.mId);
 
-    sp<SoundTrigger> module = SoundTrigger::attach(handle, callback);
+    sp<SoundTrigger> module = SoundTrigger::attach(opPackageNameString16, handle, callback);
     if (module == 0) {
         return;
     }
@@ -816,14 +824,14 @@
 
 static const JNINativeMethod gMethods[] = {
     {"listModules",
-        "(Ljava/util/ArrayList;)I",
+        "(Ljava/lang/String;Ljava/util/ArrayList;)I",
         (void *)android_hardware_SoundTrigger_listModules},
 };
 
 
 static const JNINativeMethod gModuleMethods[] = {
     {"native_setup",
-        "(Ljava/lang/Object;)V",
+        "(Ljava/lang/String;Ljava/lang/Object;)V",
         (void *)android_hardware_SoundTrigger_setup},
     {"native_finalize",
         "()V",
diff --git a/core/jni/android_os_SELinux.cpp b/core/jni/android_os_SELinux.cpp
index 8cb1078..236ee61 100644
--- a/core/jni/android_os_SELinux.cpp
+++ b/core/jni/android_os_SELinux.cpp
@@ -24,10 +24,30 @@
 #include "selinux/android.h"
 #include <errno.h>
 #include <memory>
+#include <atomic>
 #include <nativehelper/ScopedLocalRef.h>
 #include <nativehelper/ScopedUtfChars.h>
 
 namespace android {
+namespace {
+std::atomic<selabel_handle*> sehandle{nullptr};
+
+selabel_handle* GetSELabelHandle() {
+    selabel_handle* h = sehandle.load();
+    if (h != nullptr) {
+        return h;
+    }
+
+    h = selinux_android_file_context_handle();
+    selabel_handle* expected = nullptr;
+    if (!sehandle.compare_exchange_strong(expected, h)) {
+        selabel_close(h);
+        return sehandle.load();
+    }
+    return h;
+}
+
+}
 
 struct SecurityContext_Delete {
     void operator()(security_context_t p) const {
@@ -60,6 +80,44 @@
     return (security_getenforce() == 1) ? true : false;
 }
 
+static jstring fileSelabelLookup(JNIEnv* env, jobject, jstring pathStr) {
+    if (isSELinuxDisabled) {
+        ALOGE("fileSelabelLookup => SELinux is disabled");
+        return NULL;
+    }
+
+    if (pathStr == NULL) {
+      ALOGE("fileSelabelLookup => got null path.");
+      jniThrowNullPointerException(
+          env, "Trying to get security context of a null path.");
+      return NULL;
+    }
+
+    ScopedUtfChars path(env, pathStr);
+    const char* path_c_str = path.c_str();
+    if (path_c_str == NULL) {
+        ALOGE("fileSelabelLookup => Got null path");
+        jniThrowNullPointerException(
+            env, "Trying to get security context of a null path.");
+        return NULL;
+    }
+
+    auto* selabel_handle = GetSELabelHandle();
+    if (selabel_handle == NULL) {
+        ALOGE("fileSelabelLookup => Failed to get SEHandle");
+        return NULL;
+    }
+
+    security_context_t tmp = NULL;
+    if (selabel_lookup(selabel_handle, &tmp, path_c_str, S_IFREG) != 0) {
+      ALOGE("fileSelabelLookup => selabel_lookup for %s failed: %d", path_c_str, errno);
+      return NULL;
+    }
+
+    Unique_SecurityContext context(tmp);
+    return env->NewStringUTF(context.get());
+}
+
 static jstring getFdConInner(JNIEnv *env, jobject fileDescriptor, bool isSocket) {
     if (isSELinuxDisabled) {
         return NULL;
@@ -354,6 +412,7 @@
     { "native_restorecon"        , "(Ljava/lang/String;I)Z"                       , (void*)native_restorecon},
     { "setFileContext"           , "(Ljava/lang/String;Ljava/lang/String;)Z"      , (void*)setFileCon       },
     { "setFSCreateContext"       , "(Ljava/lang/String;)Z"                        , (void*)setFSCreateCon   },
+    { "fileSelabelLookup"        , "(Ljava/lang/String;)Ljava/lang/String;"       , (void*)fileSelabelLookup},
 };
 
 static int log_callback(int type, const char *fmt, ...) {
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index bf56ef4..d3f9196 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -163,7 +163,7 @@
       }
 
       // Generic idmap parameters
-      const char* argv[8];
+      const char* argv[9];
       int argc = 0;
       struct stat st;
 
@@ -199,6 +199,10 @@
         argv[argc++] = AssetManager::PRODUCT_SERVICES_OVERLAY_DIR;
       }
 
+      if (stat(AssetManager::ODM_OVERLAY_DIR, &st) == 0) {
+        argv[argc++] = AssetManager::ODM_OVERLAY_DIR;
+      }
+
       // Finally, invoke idmap (if any overlay directory exists)
       if (argc > 5) {
         execv(AssetManager::IDMAP_BIN, (char* const*)argv);
@@ -233,6 +237,10 @@
     input_dirs.push_back(AssetManager::PRODUCT_SERVICES_OVERLAY_DIR);
   }
 
+  if (stat(AssetManager::ODM_OVERLAY_DIR, &st) == 0) {
+    input_dirs.push_back(AssetManager::ODM_OVERLAY_DIR);
+  }
+
   if (input_dirs.empty()) {
     LOG(WARNING) << "no directories for idmap2 to scan";
     return env->NewObjectArray(0, g_stringClass, nullptr);
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 70b3436..430b9c5 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -126,7 +126,7 @@
 static jclass gZygoteInitClass;
 static jmethodID gCreateSystemServerClassLoader;
 
-static bool g_is_security_enforced = true;
+static bool gIsSecurityEnforced = true;
 
 /**
  * The maximum number of characters (not including a null terminator) that a
@@ -510,7 +510,7 @@
 }
 
 static void SetUpSeccompFilter(uid_t uid, bool is_child_zygote) {
-  if (!g_is_security_enforced) {
+  if (!gIsSecurityEnforced) {
     ALOGI("seccomp disabled by setenforce 0");
     return;
   }
@@ -1626,16 +1626,48 @@
   return fd_vec;
 }
 
+static void UnmountStorageOnInit(JNIEnv* env) {
+  // Zygote process unmount root storage space initially before every child processes are forked.
+  // Every forked child processes (include SystemServer) only mount their own root storage space
+  // and no need unmount storage operation in MountEmulatedStorage method.
+  // Zygote process does not utilize root storage spaces and unshares its mount namespace below.
+
+  // See storage config details at http://source.android.com/tech/storage/
+  // Create private mount namespace shared by all children
+  if (unshare(CLONE_NEWNS) == -1) {
+    RuntimeAbort(env, __LINE__, "Failed to unshare()");
+    return;
+  }
+
+  // Mark rootfs as being a slave so that changes from default
+  // namespace only flow into our children.
+  if (mount("rootfs", "/", nullptr, (MS_SLAVE | MS_REC), nullptr) == -1) {
+    RuntimeAbort(env, __LINE__, "Failed to mount() rootfs as MS_SLAVE");
+    return;
+  }
+
+  // Create a staging tmpfs that is shared by our children; they will
+  // bind mount storage into their respective private namespaces, which
+  // are isolated from each other.
+  const char* target_base = getenv("EMULATED_STORAGE_TARGET");
+  if (target_base != nullptr) {
+#define STRINGIFY_UID(x) __STRING(x)
+    if (mount("tmpfs", target_base, "tmpfs", MS_NOSUID | MS_NODEV,
+              "uid=0,gid=" STRINGIFY_UID(AID_SDCARD_R) ",mode=0751") == -1) {
+      ALOGE("Failed to mount tmpfs to %s", target_base);
+      RuntimeAbort(env, __LINE__, "Failed to mount tmpfs");
+      return;
+    }
+#undef STRINGIFY_UID
+  }
+
+  UnmountTree("/storage");
+}
+
 }  // anonymous namespace
 
 namespace android {
 
-static void com_android_internal_os_Zygote_nativeSecurityInit(JNIEnv*, jclass) {
-  // security_getenforce is not allowed on app process. Initialize and cache
-  // the value before zygote forks.
-  g_is_security_enforced = security_getenforce();
-}
-
 static void com_android_internal_os_Zygote_nativePreApplicationInit(JNIEnv*, jclass) {
   PreApplicationInit();
 }
@@ -1789,47 +1821,9 @@
     FileDescriptorWhitelist::Get()->Allow(path_cstr);
 }
 
-static void com_android_internal_os_Zygote_nativeUnmountStorageOnInit(JNIEnv* env, jclass) {
-    // Zygote process unmount root storage space initially before every child processes are forked.
-    // Every forked child processes (include SystemServer) only mount their own root storage space
-    // and no need unmount storage operation in MountEmulatedStorage method.
-    // Zygote process does not utilize root storage spaces and unshares its mount namespace below.
-
-    // See storage config details at http://source.android.com/tech/storage/
-    // Create private mount namespace shared by all children
-    if (unshare(CLONE_NEWNS) == -1) {
-        RuntimeAbort(env, __LINE__, "Failed to unshare()");
-        return;
-    }
-
-    // Mark rootfs as being a slave so that changes from default
-    // namespace only flow into our children.
-    if (mount("rootfs", "/", nullptr, (MS_SLAVE | MS_REC), nullptr) == -1) {
-        RuntimeAbort(env, __LINE__, "Failed to mount() rootfs as MS_SLAVE");
-        return;
-    }
-
-    // Create a staging tmpfs that is shared by our children; they will
-    // bind mount storage into their respective private namespaces, which
-    // are isolated from each other.
-    const char* target_base = getenv("EMULATED_STORAGE_TARGET");
-    if (target_base != nullptr) {
-#define STRINGIFY_UID(x) __STRING(x)
-        if (mount("tmpfs", target_base, "tmpfs", MS_NOSUID | MS_NODEV,
-                  "uid=0,gid=" STRINGIFY_UID(AID_SDCARD_R) ",mode=0751") == -1) {
-            ALOGE("Failed to mount tmpfs to %s", target_base);
-            RuntimeAbort(env, __LINE__, "Failed to mount tmpfs");
-            return;
-        }
-#undef STRINGIFY_UID
-    }
-
-    UnmountTree("/storage");
-}
-
 static void com_android_internal_os_Zygote_nativeInstallSeccompUidGidFilter(
         JNIEnv* env, jclass, jint uidGidMin, jint uidGidMax) {
-  if (!g_is_security_enforced) {
+  if (!gIsSecurityEnforced) {
     ALOGI("seccomp disabled by setenforce 0");
     return;
   }
@@ -1881,8 +1875,12 @@
  * @param is_primary  If this process is the primary or secondary Zygote; used to compute the name
  * of the environment variable storing the file descriptors.
  */
-static void com_android_internal_os_Zygote_nativeGetSocketFDs(JNIEnv* env, jclass,
-                                                              jboolean is_primary) {
+static void com_android_internal_os_Zygote_nativeInitNativeState(JNIEnv* env, jclass,
+                                                                 jboolean is_primary) {
+  /*
+   * Obtain file descriptors created by init from the environment.
+   */
+
   std::string android_socket_prefix(ANDROID_SOCKET_PREFIX);
   std::string env_var_name = android_socket_prefix + (is_primary ? "zygote" : "zygote_secondary");
   char* env_var_val = getenv(env_var_name.c_str());
@@ -1903,6 +1901,30 @@
   } else {
     ALOGE("Unable to fetch USAP pool socket file descriptor");
   }
+
+  /*
+   * Security Initialization
+   */
+
+  // security_getenforce is not allowed on app process. Initialize and cache
+  // the value before zygote forks.
+  gIsSecurityEnforced = security_getenforce();
+
+  selinux_android_seapp_context_init();
+
+  /*
+   * Storage Initialization
+   */
+
+  UnmountStorageOnInit(env);
+
+  /*
+   * Performance Initialization
+   */
+
+  if (!SetTaskProfiles(0, {})) {
+    ZygoteFailure(env, "zygote", nullptr, "Zygote SetTaskProfiles failed");
+  }
 }
 
 /**
@@ -1999,8 +2021,6 @@
 }
 
 static const JNINativeMethod gMethods[] = {
-    { "nativeSecurityInit", "()V",
-      (void *) com_android_internal_os_Zygote_nativeSecurityInit },
     { "nativeForkAndSpecialize",
       "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)I",
       (void *) com_android_internal_os_Zygote_nativeForkAndSpecialize },
@@ -2008,8 +2028,6 @@
       (void *) com_android_internal_os_Zygote_nativeForkSystemServer },
     { "nativeAllowFileAcrossFork", "(Ljava/lang/String;)V",
       (void *) com_android_internal_os_Zygote_nativeAllowFileAcrossFork },
-    { "nativeUnmountStorageOnInit", "()V",
-      (void *) com_android_internal_os_Zygote_nativeUnmountStorageOnInit },
     { "nativePreApplicationInit", "()V",
       (void *) com_android_internal_os_Zygote_nativePreApplicationInit },
     { "nativeInstallSeccompUidGidFilter", "(II)V",
@@ -2019,8 +2037,8 @@
     { "nativeSpecializeAppProcess",
       "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V",
       (void *) com_android_internal_os_Zygote_nativeSpecializeAppProcess },
-    { "nativeGetSocketFDs", "(Z)V",
-      (void *) com_android_internal_os_Zygote_nativeGetSocketFDs },
+    { "nativeInitNativeState", "(Z)V",
+      (void *) com_android_internal_os_Zygote_nativeInitNativeState },
     { "nativeGetUsapPipeFDs", "()[I",
       (void *) com_android_internal_os_Zygote_nativeGetUsapPipeFDs },
     { "nativeRemoveUsapTableEntry", "(I)Z",
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index d8d4656..0996352 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -102,6 +102,8 @@
   static const char* kProductOverlayDir = "/product/overlay";
   static const char* kSystemProductServicesOverlayDir = "/system/product_services/overlay/";
   static const char* kProductServicesOverlayDir = "/product_services/overlay";
+  static const char* kSystemOdmOverlayDir = "/system/odm/overlay";
+  static const char* kOdmOverlayDir = "/odm/overlay";
   static const char* kApkSuffix = ".apk";
 
   if ((android::base::StartsWith(path, kOverlayDir)
@@ -110,7 +112,9 @@
        || android::base::StartsWith(path, kSystemProductOverlayDir)
        || android::base::StartsWith(path, kProductOverlayDir)
        || android::base::StartsWith(path, kSystemProductServicesOverlayDir)
-       || android::base::StartsWith(path, kProductServicesOverlayDir))
+       || android::base::StartsWith(path, kProductServicesOverlayDir)
+       || android::base::StartsWith(path, kSystemOdmOverlayDir)
+       || android::base::StartsWith(path, kOdmOverlayDir))
       && android::base::EndsWith(path, kApkSuffix)
       && path.find("/../") == std::string::npos) {
     return true;
diff --git a/core/proto/android/app/enums.proto b/core/proto/android/app/enums.proto
index 1754e42..9abe923 100644
--- a/core/proto/android/app/enums.proto
+++ b/core/proto/android/app/enums.proto
@@ -38,6 +38,7 @@
 }
 
 // ActivityManager.java PROCESS_STATEs
+// Next tag: 1021
 enum ProcessStateEnum {
     // Unlike the ActivityManager PROCESS_STATE values, the ordering and numerical values
     // here are completely fixed and arbitrary. Order is irrelevant.
@@ -56,9 +57,11 @@
     // Process is hosting the current top activities. Note that this covers
     // all activities that are visible to the user.
     PROCESS_STATE_TOP = 1002;
+    // Process is bound to a TOP app.
+    PROCESS_STATE_BOUND_TOP = 1020;
     // Process is hosting a foreground service.
     PROCESS_STATE_FOREGROUND_SERVICE = 1003;
-    // Process is hosting a foreground service due to a system binding.
+    // Process is hosting a service bound by the system or another foreground app.
     PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 1004;
     // Process is important to the user, and something they are aware of.
     PROCESS_STATE_IMPORTANT_FOREGROUND = 1005;
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index d029527..ace88f5 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2331,4 +2331,9 @@
 
     // Open: Settings > app > bubble settings > confirmation dialog
     DIALOG_APP_BUBBLE_SETTINGS = 1702;
+
+    // ACTION: Display white balance setting enabled or disabled.
+    // CATEGORY: SETTINGS
+    // OS: Q
+    ACTION_DISPLAY_WHITE_BALANCE_SETTING_CHANGED = 1703;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3788f47..fb92fbf 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4506,6 +4506,10 @@
     <permission android:name="android.permission.ACCESS_SHARED_LIBRARIES"
                 android:protectionLevel="signature|installer" />
 
+    <!-- Allows input events to be monitored. Very dangerous!  @hide -->
+    <permission android:name="android.permission.MONITOR_INPUT"
+                android:protectionLevel="signature" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
diff --git a/core/res/res/layout-car/car_preference.xml b/core/res/res/layout-car/car_preference.xml
index 939c3fb..ae3d63b 100644
--- a/core/res/res/layout-car/car_preference.xml
+++ b/core/res/res/layout-car/car_preference.xml
@@ -20,7 +20,7 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:background="?android:attr/selectableItemBackground"
-    android:focusable="true"
+    android:clipToPadding="false"
     android:minHeight="?android:attr/listPreferredItemHeightSmall"
     android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
     android:paddingStart="?android:attr/listPreferredItemPaddingStart">
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 726dccb..ec2a6ae 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2442,6 +2442,16 @@
     <!-- Maximum number of users we allow to be running at a time -->
     <integer name="config_multiuserMaxRunningUsers">3</integer>
 
+    <!-- Whether to delay user data locking for background user.
+         If false, user switched-out from user switching will still be in running state until
+         config_multiuserMaxRunningUsers is reached. Once config_multiuserMaxRunningUsers is
+         reached, user will be stopped and user data is locked.
+         If true, user switched out from user switching will always be stopped but its user data
+         is not locked. Total number of unlocked users will be limited by
+         config_multiuserMaxRunningUsers. Once that limit is reached, least recently stopped user
+         will be locked. -->
+    <bool name="config_multiuserDelayUserDataLocking">false</bool>
+
     <!-- Whether UI for multi user should be shown -->
     <bool name="config_enableMultiUserUI">false</bool>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index b92033e..4b8a96c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1567,7 +1567,7 @@
     <string name="face_error_lockout_permanent">Too many attempts. Face authentication disabled.</string>
     <!-- Generic error message shown when the face hardware can't recognize the face. [CHAR LIMIT=50] -->
     <string name="face_error_unable_to_process">Can\u2019t verify face. Try again.</string>
-    <!-- Generic error message shown when the user has no enrolled face. [CHAR LIMIT=50] -->
+    <!-- Generic error message shown when the user has no enrolled face. [CHAR LIMIT=52] -->
     <string name="face_error_not_enrolled">You haven\u2019t set up face authentication</string>
     <!-- Generic error message shown when the app requests face authentication on a device without a sensor. [CHAR LIMIT=60] -->
     <string name="face_error_hw_not_present">Face authentication is not supported on this device</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a29a4f8..3e23640 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -498,6 +498,7 @@
   <java-symbol type="integer" name="config_lockSoundVolumeDb" />
   <java-symbol type="integer" name="config_multiuserMaximumUsers" />
   <java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
+  <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
   <java-symbol type="integer" name="config_safe_media_volume_index" />
   <java-symbol type="integer" name="config_safe_media_volume_usb_mB" />
   <java-symbol type="integer" name="config_mobile_mtu" />
diff --git a/core/tests/BroadcastRadioTests/Android.bp b/core/tests/BroadcastRadioTests/Android.bp
new file mode 100644
index 0000000..0aebaaa
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "BroadcastRadioTests",
+    privileged: true,
+    certificate: "platform",
+    // TODO(b/13282254): uncomment when b/13282254 is fixed
+    // sdk_version: "current"
+    platform_apis: true,
+    static_libs: [
+        "compatibility-device-util-axt",
+        "androidx.test.rules",
+        "testng",
+    ],
+    libs: ["android.test.base"],
+    srcs: ["src/**/*.java"],
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/core/tests/BroadcastRadioTests/Android.mk b/core/tests/BroadcastRadioTests/Android.mk
deleted file mode 100644
index faffc4b..0000000
--- a/core/tests/BroadcastRadioTests/Android.mk
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := BroadcastRadioTests
-
-LOCAL_PRIVILEGED_MODULE := true
-LOCAL_CERTIFICATE := platform
-LOCAL_MODULE_TAGS := tests
-# TODO(b/13282254): uncomment when b/13282254 is fixed
-# LOCAL_SDK_VERSION := current
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util-axt androidx.test.rules testng
-
-LOCAL_JAVA_LIBRARIES := android.test.base
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_DEX_PREOPT := false
-LOCAL_PROGUARD_ENABLED := disabled
-
-include $(BUILD_PACKAGE)
diff --git a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java b/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
index 08f9de6..8081e9e 100644
--- a/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsValidatorsTest.java
@@ -16,6 +16,8 @@
 
 package android.provider;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -26,6 +28,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.json.JSONException;
+import org.json.JSONObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -218,6 +222,31 @@
     }
 
     @Test
+    public void testJSONObjectValidator() throws JSONException {
+        Validator v = SettingsValidators.JSON_OBJECT_VALIDATOR;
+
+        assertThat(v.validate(new JSONObject().toString())).isTrue();
+        assertThat(v.validate("{}")).isTrue();
+        assertThat(v.validate(new JSONObject().put("foo", "bar").toString()))
+                .isTrue();
+        assertThat(v.validate("{\"foo\": \"bar\"}")).isTrue();
+
+        assertThat(v.validate("random string")).isFalse();
+        assertThat(v.validate("random: string")).isFalse();
+        assertThat(v.validate("{random: }")).isFalse();
+    }
+
+    @Test
+    public void testJSONObjectValidator_onNullValue_returnsFalse() {
+        assertThat(SettingsValidators.JSON_OBJECT_VALIDATOR.validate(null)).isFalse();
+    }
+
+    @Test
+    public void testJSONObjectValidator_onEmptyString_returnsFalse() {
+        assertThat(SettingsValidators.JSON_OBJECT_VALIDATOR.validate("")).isFalse();
+    }
+
+    @Test
     public void ensureAllBackedUpSystemSettingsHaveValidators() {
         String offenders = getOffenders(concat(Settings.System.SETTINGS_TO_BACKUP,
                 Settings.System.LEGACY_RESTORE_SETTINGS), Settings.System.VALIDATORS);
diff --git a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
index 67423c8..db5f82a 100644
--- a/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/ActionsSuggestionsHelperTest.java
@@ -263,6 +263,7 @@
                                         "title",
                                         null,
                                         "description",
+                                        null,
                                         Intent.ACTION_VIEW,
                                         Uri.parse("http://www.android.com").toString(),
                                         null,
diff --git a/core/tests/coretests/src/android/view/textclassifier/FakeContextBuilder.java b/core/tests/coretests/src/android/view/textclassifier/FakeContextBuilder.java
index 2674e37..11bea0c 100644
--- a/core/tests/coretests/src/android/view/textclassifier/FakeContextBuilder.java
+++ b/core/tests/coretests/src/android/view/textclassifier/FakeContextBuilder.java
@@ -54,6 +54,7 @@
     private final PackageManager mPackageManager;
     private final ContextWrapper mContext;
     private final Map<String, ComponentName> mComponents = new HashMap<>();
+    private final Map<String, CharSequence> mAppLabels = new HashMap<>();
     private @Nullable ComponentName mAllIntentComponent;
 
     public FakeContextBuilder() {
@@ -79,6 +80,14 @@
         return this;
     }
 
+    /**
+     * Sets the app label res for a specified package.
+     */
+    public FakeContextBuilder setAppLabel(String packageName, @Nullable CharSequence appLabel) {
+        Preconditions.checkNotNull(packageName);
+        mAppLabels.put(packageName, appLabel);
+        return this;
+    }
 
     /**
      * Sets the component name of an activity to handle all intents.
@@ -102,6 +111,11 @@
                             : mAllIntentComponent;
                     return getResolveInfo(component);
                 });
+        when(mPackageManager.getApplicationLabel(any(ApplicationInfo.class))).thenAnswer(
+                (Answer<CharSequence>) invocation -> {
+                    ApplicationInfo applicationInfo = invocation.getArgument(0);
+                    return mAppLabels.get(applicationInfo.packageName);
+                });
         return mContext;
     }
 
@@ -125,6 +139,7 @@
             info.activityInfo.name = component.getClassName();
             info.activityInfo.exported = true;
             info.activityInfo.applicationInfo = new ApplicationInfo();
+            info.activityInfo.applicationInfo.packageName = component.getPackageName();
             info.activityInfo.applicationInfo.icon = 0;
         }
         return info;
diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java
index 857408f..3ad26f5 100644
--- a/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/intent/LabeledIntentTest.java
@@ -20,6 +20,7 @@
 
 import static org.testng.Assert.assertThrows;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
@@ -40,17 +41,21 @@
     private static final String TITLE_WITHOUT_ENTITY = "Map";
     private static final String TITLE_WITH_ENTITY = "Map NW14D1";
     private static final String DESCRIPTION = "Check the map";
+    private static final String DESCRIPTION_WITH_APP_NAME = "Use %1$s to open map";
     private static final Intent INTENT =
             new Intent(Intent.ACTION_VIEW).setDataAndNormalize(Uri.parse("http://www.android.com"));
     private static final int REQUEST_CODE = 42;
     private static final Bundle TEXT_LANGUAGES_BUNDLE = Bundle.EMPTY;
+    private static final String APP_LABEL = "fake";
 
     private Context mContext;
 
     @Before
     public void setup() {
+        final ComponentName component = FakeContextBuilder.DEFAULT_COMPONENT;
         mContext = new FakeContextBuilder()
-                .setIntentComponent(Intent.ACTION_VIEW, FakeContextBuilder.DEFAULT_COMPONENT)
+                .setIntentComponent(Intent.ACTION_VIEW, component)
+                .setAppLabel(component.getPackageName(), APP_LABEL)
                 .build();
     }
 
@@ -60,6 +65,7 @@
                 TITLE_WITHOUT_ENTITY,
                 TITLE_WITH_ENTITY,
                 DESCRIPTION,
+                null,
                 INTENT,
                 REQUEST_CODE
         );
@@ -82,6 +88,7 @@
                 TITLE_WITHOUT_ENTITY,
                 null,
                 DESCRIPTION,
+                null,
                 INTENT,
                 REQUEST_CODE
         );
@@ -103,6 +110,7 @@
                 TITLE_WITHOUT_ENTITY,
                 null,
                 DESCRIPTION,
+                null,
                 INTENT,
                 REQUEST_CODE
         );
@@ -124,6 +132,7 @@
                 TITLE_WITHOUT_ENTITY,
                 null,
                 DESCRIPTION,
+                null,
                 INTENT,
                 REQUEST_CODE
         );
@@ -148,6 +157,7 @@
                                 null,
                                 null,
                                 DESCRIPTION,
+                                null,
                                 INTENT,
                                 REQUEST_CODE
                         ));
@@ -161,6 +171,7 @@
                 TITLE_WITHOUT_ENTITY,
                 null,
                 DESCRIPTION,
+                null,
                 unresolvableIntent,
                 REQUEST_CODE);
 
@@ -168,4 +179,22 @@
 
         assertThat(result).isNull();
     }
+
+    @Test
+    public void resolve_descriptionWithAppName() {
+        LabeledIntent labeledIntent = new LabeledIntent(
+                TITLE_WITHOUT_ENTITY,
+                TITLE_WITH_ENTITY,
+                DESCRIPTION,
+                DESCRIPTION_WITH_APP_NAME,
+                INTENT,
+                REQUEST_CODE
+        );
+
+        LabeledIntent.Result result = labeledIntent.resolve(
+                mContext, /*titleChooser*/ null, TEXT_LANGUAGES_BUNDLE);
+
+        assertThat(result).isNotNull();
+        assertThat(result.remoteAction.getContentDescription()).isEqualTo("Use fake to open map");
+    }
 }
diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java
index 2e97e638..b9a1a8c 100644
--- a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateClassificationIntentFactoryTest.java
@@ -50,6 +50,7 @@
     private static final String TEXT = "text";
     private static final String TITLE_WITHOUT_ENTITY = "Map";
     private static final String DESCRIPTION = "Opens in Maps";
+    private static final String DESCRIPTION_WITH_APP_NAME = "Use %1$s to open Map";
     private static final String ACTION = Intent.ACTION_VIEW;
 
     @Mock
@@ -219,6 +220,7 @@
                         TITLE_WITHOUT_ENTITY,
                         null,
                         DESCRIPTION,
+                        DESCRIPTION_WITH_APP_NAME,
                         ACTION,
                         null,
                         null,
diff --git a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java
index 6e3de2d..a33c358 100644
--- a/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/intent/TemplateIntentFactoryTest.java
@@ -41,6 +41,7 @@
     private static final String TITLE_WITHOUT_ENTITY = "Map";
     private static final String TITLE_WITH_ENTITY = "Map NW14D1";
     private static final String DESCRIPTION = "Check the map";
+    private static final String DESCRIPTION_WITH_APP_NAME = "Use %1$s to open map";
     private static final String ACTION = Intent.ACTION_VIEW;
     private static final String DATA = Uri.parse("http://www.android.com").toString();
     private static final String TYPE = "text/html";
@@ -73,6 +74,7 @@
                 TITLE_WITHOUT_ENTITY,
                 TITLE_WITH_ENTITY,
                 DESCRIPTION,
+                DESCRIPTION_WITH_APP_NAME,
                 ACTION,
                 DATA,
                 TYPE,
@@ -91,6 +93,7 @@
         assertThat(labeledIntent.titleWithoutEntity).isEqualTo(TITLE_WITHOUT_ENTITY);
         assertThat(labeledIntent.titleWithEntity).isEqualTo(TITLE_WITH_ENTITY);
         assertThat(labeledIntent.description).isEqualTo(DESCRIPTION);
+        assertThat(labeledIntent.descriptionWithAppName).isEqualTo(DESCRIPTION_WITH_APP_NAME);
         assertThat(labeledIntent.requestCode).isEqualTo(REQUEST_CODE);
         Intent intent = labeledIntent.intent;
         assertThat(intent.getAction()).isEqualTo(ACTION);
@@ -109,6 +112,7 @@
                 TITLE_WITHOUT_ENTITY,
                 TITLE_WITH_ENTITY,
                 DESCRIPTION,
+                DESCRIPTION_WITH_APP_NAME,
                 ACTION,
                 "HTTp://www.android.com",
                 TYPE,
@@ -132,6 +136,7 @@
                 TITLE_WITHOUT_ENTITY,
                 null,
                 DESCRIPTION,
+                null,
                 ACTION,
                 null,
                 null,
@@ -177,6 +182,7 @@
                 TITLE_WITHOUT_ENTITY,
                 TITLE_WITH_ENTITY,
                 DESCRIPTION,
+                DESCRIPTION_WITH_APP_NAME,
                 ACTION,
                 DATA,
                 TYPE,
@@ -199,6 +205,7 @@
                 null,
                 null,
                 DESCRIPTION,
+                DESCRIPTION_WITH_APP_NAME,
                 ACTION,
                 null,
                 null,
@@ -221,6 +228,7 @@
                 TITLE_WITHOUT_ENTITY,
                 TITLE_WITH_ENTITY,
                 null,
+                null,
                 ACTION,
                 null,
                 null,
@@ -243,6 +251,7 @@
                 TITLE_WITHOUT_ENTITY,
                 TITLE_WITH_ENTITY,
                 DESCRIPTION,
+                DESCRIPTION_WITH_APP_NAME,
                 null,
                 null,
                 null,
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 185fa07..00b4a22 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -39,6 +39,7 @@
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.ClipboardManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
@@ -47,8 +48,10 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.drawable.Icon;
 import android.metrics.LogMaker;
 import android.net.Uri;
+import android.service.chooser.ChooserTarget;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -735,6 +738,120 @@
         onView(withId(R.id.content_preview_file_icon)).check(matches(isDisplayed()));
     }
 
+    // This test is too long and too slow and should not be taken as an example for future tests.
+    // This is necessary because it tests that multiple calls result in the same result but
+    // normally a test this long should be broken into smaller tests testing individual components.
+    @Test
+    public void testDirectTargetSelectionLogging() throws InterruptedException {
+        Intent sendIntent = createSendTextIntent();
+        // We need app targets for direct targets to get displayed
+        List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+        when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+                Mockito.anyBoolean(),
+                Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+        // Set up resources
+        MetricsLogger mockLogger = sOverrides.metricsLogger;
+        ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
+        // Create direct share target
+        List<ChooserTarget> serviceTargets = createDirectShareTargets(1);
+        ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+
+        // Start activity
+        final ChooserWrapperActivity activity = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent, null));
+
+        // Insert the direct share target
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> activity.getAdapter().addServiceResults(
+                        activity.createTestDisplayResolveInfo(sendIntent,
+                                ri,
+                                "testLabel",
+                                "testInfo",
+                                sendIntent),
+                        serviceTargets,
+                        false)
+        );
+        // Thread.sleep shouldn't be a thing in an integration test but it's
+        // necessary here because of the way the code is structured
+        // TODO: restructure the tests b/129870719
+        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+        assertThat("Chooser should have 3 targets (2apps, 1 direct)",
+                activity.getAdapter().getCount(), is(3));
+        assertThat("Chooser should have exactly one selectable direct target",
+                activity.getAdapter().getSelectableServiceTargetCount(), is(1));
+        assertThat("The resolver info must match the resolver info used to create the target",
+                activity.getAdapter().getItem(0).getResolveInfo(), is(ri));
+
+        // Click on the direct target
+        String name = serviceTargets.get(0).getTitle().toString();
+        onView(withText(name))
+                .perform(click());
+        waitForIdle();
+
+        // Currently we're seeing 3 invocations
+        //      1. ChooserActivity.onCreate()
+        //      2. ChooserActivity$ChooserRowAdapter.createContentPreviewView()
+        //      3. ChooserActivity.startSelected -- which is the one we're after
+        verify(mockLogger, Mockito.times(3)).write(logMakerCaptor.capture());
+        assertThat(logMakerCaptor.getAllValues().get(2).getCategory(),
+                is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET));
+        String hashedName = (String) logMakerCaptor
+                .getAllValues().get(2).getTaggedData(MetricsEvent.FIELD_HASHED_TARGET_NAME);
+        assertThat("Hash is not predictable but must be obfuscated",
+                hashedName, is(not(name)));
+
+        // Running the same again to check if the hashed name is the same as before.
+
+        Intent sendIntent2 = createSendTextIntent();
+
+        // Start activity
+        final ChooserWrapperActivity activity2 = mActivityRule
+                .launchActivity(Intent.createChooser(sendIntent2, null));
+        waitForIdle();
+
+        // Insert the direct share target
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(
+                () -> activity2.getAdapter().addServiceResults(
+                        activity2.createTestDisplayResolveInfo(sendIntent,
+                                ri,
+                                "testLabel",
+                                "testInfo",
+                                sendIntent),
+                        serviceTargets,
+                        false)
+        );
+        // Thread.sleep shouldn't be a thing in an integration test but it's
+        // necessary here because of the way the code is structured
+        // TODO: restructure the tests b/129870719
+        Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+        assertThat("Chooser should have 3 targets (2apps, 1 direct)",
+                activity2.getAdapter().getCount(), is(3));
+        assertThat("Chooser should have exactly one selectable direct target",
+                activity2.getAdapter().getSelectableServiceTargetCount(), is(1));
+        assertThat("The resolver info must match the resolver info used to create the target",
+                activity2.getAdapter().getItem(0).getResolveInfo(), is(ri));
+
+        // Click on the direct target
+        onView(withText(name))
+                .perform(click());
+        waitForIdle();
+
+        // Currently we're seeing 6 invocations (3 from above, doubled up)
+        //      4. ChooserActivity.onCreate()
+        //      5. ChooserActivity$ChooserRowAdapter.createContentPreviewView()
+        //      6. ChooserActivity.startSelected -- which is the one we're after
+        verify(mockLogger, Mockito.times(6)).write(logMakerCaptor.capture());
+        assertThat(logMakerCaptor.getAllValues().get(5).getCategory(),
+                is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET));
+        String hashedName2 = (String) logMakerCaptor
+                .getAllValues().get(5).getTaggedData(MetricsEvent.FIELD_HASHED_TARGET_NAME);
+        assertThat("Hashing the same name should result in the same hashed value",
+                hashedName2, is(hashedName));
+    }
+
     private Intent createSendTextIntent() {
         Intent sendIntent = new Intent();
         sendIntent.setAction(Intent.ACTION_SEND);
@@ -798,6 +915,23 @@
         return infoList;
     }
 
+    private List<ChooserTarget> createDirectShareTargets(int numberOfResults) {
+        Icon icon = Icon.createWithBitmap(createBitmap());
+        String testTitle = "testTitle";
+        List<ChooserTarget> targets = new ArrayList<>();
+        for (int i = 0; i < numberOfResults; i++) {
+            ComponentName componentName = ResolverDataProvider.createComponentName(i);
+            ChooserTarget tempTarget = new ChooserTarget(
+                    testTitle + i,
+                    icon,
+                    (float) (1 - ((i + 1) / 10.0)),
+                    componentName,
+                    null);
+            targets.add(tempTarget);
+        }
+        return targets;
+    }
+
     private void waitForIdle() {
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 5e71129..44e56ea 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -21,7 +21,9 @@
 import android.app.usage.UsageStatsManager;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.net.Uri;
@@ -121,6 +123,11 @@
         return super.isWorkProfile();
     }
 
+    public DisplayResolveInfo createTestDisplayResolveInfo(Intent originalIntent, ResolveInfo pri,
+            CharSequence pLabel, CharSequence pInfo, Intent pOrigIntent) {
+        return new DisplayResolveInfo(originalIntent, pri, pLabel, pInfo, pOrigIntent);
+    }
+
     /**
      * We cannot directly mock the activity created since instrumentation creates it.
      * <p>
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java
index 460fe47..b45f8d2 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java
@@ -26,8 +26,8 @@
 
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
@@ -210,6 +210,73 @@
         assertThat(threadNames(processes2)).containsExactly("thread0");
     }
 
+    @Test
+    public void test_nonNegativeOtherThreads() {
+        when(mMockReader.getProcessCpuUsage())
+                .thenReturn(createProcess(new int[] {0}, new int[] {0}))
+                .thenReturn(createProcess(new int[] {4}, new int[] {4}))
+                .thenReturn(createProcess(new int[] {10}, new int[] {7}))
+                .thenReturn(createProcess(new int[] {20}, new int[] {15}));
+        KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
+                new KernelCpuThreadReaderDiff(mMockReader, 5);
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
+
+        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes1 =
+                kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
+        assertThat(cpuUsages(processes1)).containsExactly(Collections.singletonList(8));
+        assertThat(threadNames(processes1)).containsExactly("__OTHER_THREADS");
+
+        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes2 =
+                kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
+        assertThat(cpuUsages(processes2))
+                .containsExactly(Collections.singletonList(6), Collections.singletonList(3));
+        assertThat(threadNames(processes2)).containsExactly("thread0", "__OTHER_THREADS");
+
+        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes3 =
+                kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
+        assertThat(cpuUsages(processes3))
+                .containsExactly(Collections.singletonList(10), Collections.singletonList(8));
+        assertThat(threadNames(processes3)).containsExactly("thread0", "thread1");
+    }
+
+    @Test
+    public void test_otherThreadsOnZeroDiff() {
+        when(mMockReader.getProcessCpuUsage())
+                .thenReturn(createProcess(new int[] {0}))
+                .thenReturn(createProcess(new int[] {0}));
+        KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
+                new KernelCpuThreadReaderDiff(mMockReader, 5);
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
+
+        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes1 =
+                kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
+        assertThat(cpuUsages(processes1)).containsExactly(Collections.singletonList(0));
+        assertThat(threadNames(processes1)).containsExactly("__OTHER_THREADS");
+    }
+
+    @Test
+    public void test_failureAndNewThread() {
+        when(mMockReader.getProcessCpuUsage())
+                .thenReturn(createProcess(new int[] {0}))
+                .thenThrow(new RuntimeException())
+                .thenReturn(createProcess(new int[] {1}, new int[] {10}))
+                .thenReturn(createProcess(new int[] {2}, new int[] {12}));
+        KernelCpuThreadReaderDiff kernelCpuThreadReaderDiff =
+                new KernelCpuThreadReaderDiff(mMockReader, 0);
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
+
+        assertThrows(
+                RuntimeException.class,
+                () -> cpuUsages(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()));
+        assertThat(kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed()).isNull();
+
+        ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processes1 =
+                kernelCpuThreadReaderDiff.getProcessCpuUsageDiffed();
+        assertThat(cpuUsages(processes1))
+                .containsExactly(Collections.singletonList(1), Collections.singletonList(2));
+        assertThat(threadNames(processes1)).containsExactly("thread0", "thread1");
+    }
+
     private ArrayList<KernelCpuThreadReader.ProcessCpuUsage> createProcess(
             int[]... cpuUsageMillis) {
         ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages = new ArrayList<>();
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
index ae847c1..c3e4014 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java
@@ -176,10 +176,10 @@
                 frequencies, 4);
         assertArrayEquals(
                 new int[]{1, 3, 1, 3},
-                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+                frequencyBucketCreator.bucketFrequencies(frequencies));
         assertArrayEquals(
                 new int[]{2, 2, 2, 2},
-                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+                frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
     }
 
     @Test
@@ -190,10 +190,10 @@
                 frequencies, 4);
         assertArrayEquals(
                 new int[]{1, 3, 5, 7},
-                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+                frequencyBucketCreator.bucketFrequencies(frequencies));
         assertArrayEquals(
                 new int[]{2, 2, 2, 2},
-                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+                frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
     }
 
     @Test
@@ -204,10 +204,10 @@
                 frequencies, 4);
         assertArrayEquals(
                 new int[]{1, 3, 1, 2},
-                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+                frequencyBucketCreator.bucketFrequencies(frequencies));
         assertArrayEquals(
                 new int[]{2, 3, 1, 2},
-                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+                frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
     }
 
     @Test
@@ -218,10 +218,10 @@
                 frequencies, 4);
         assertArrayEquals(
                 new int[]{1, 2, 1, 3},
-                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+                frequencyBucketCreator.bucketFrequencies(frequencies));
         assertArrayEquals(
                 new int[]{1, 2, 2, 3},
-                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+                frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
     }
 
     @Test
@@ -232,10 +232,10 @@
                 frequencies, 8);
         assertArrayEquals(
                 new int[]{1, 2, 3, 4, 1, 2, 3, 4},
-                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+                frequencyBucketCreator.bucketFrequencies(frequencies));
         assertArrayEquals(
                 new int[]{1, 1, 1, 1, 1, 1, 1, 1},
-                frequencyBucketCreator.getBucketedValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
+                frequencyBucketCreator.bucketValues(new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
     }
 
     @Test
@@ -246,10 +246,10 @@
                 frequencies, 8);
         assertArrayEquals(
                 new int[]{1, 3, 5, 7, 1, 2, 3},
-                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+                frequencyBucketCreator.bucketFrequencies(frequencies));
         assertArrayEquals(
                 new int[]{2, 2, 2, 3, 1, 1, 1},
-                frequencyBucketCreator.getBucketedValues(
+                frequencyBucketCreator.bucketValues(
                         new long[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}));
     }
 
@@ -261,39 +261,37 @@
                 frequencies, 1);
         assertArrayEquals(
                 new int[]{1},
-                frequencyBucketCreator.getBucketMinFrequencies(frequencies));
+                frequencyBucketCreator.bucketFrequencies(frequencies));
         assertArrayEquals(
                 new int[]{8},
-                frequencyBucketCreator.getBucketedValues(
+                frequencyBucketCreator.bucketValues(
                         new long[]{1, 1, 1, 1, 1, 1, 1, 1}));
     }
 
     @Test
-    public void testGetBigFrequenciesStartIndex_simple() {
-        assertEquals(
-                3, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
-                        new long[]{1, 2, 3, 1, 2, 3}));
+    public void testBucketSetup_threeClusters() {
+        long[] frequencies = {1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6};
+        KernelCpuThreadReader.FrequencyBucketCreator frequencyBucketCreator =
+                new KernelCpuThreadReader.FrequencyBucketCreator(frequencies, 6);
+        assertArrayEquals(
+                new int[] {1, 3, 2, 4, 3, 5},
+                frequencyBucketCreator.bucketFrequencies(frequencies));
+        assertArrayEquals(
+                new int[] {2, 2, 2, 2, 2, 2},
+                frequencyBucketCreator.bucketValues(
+                        new long[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}));
     }
 
     @Test
-    public void testGetBigFrequenciesStartIndex_moreLittle() {
-        assertEquals(
-                4, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
-                        new long[]{1, 2, 3, 4, 1, 2}));
-    }
-
-    @Test
-    public void testGetBigFrequenciesStartIndex_moreBig() {
-        assertEquals(
-                2, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
-                        new long[]{1, 2, 1, 2, 3, 4}));
-    }
-
-    @Test
-    public void testGetBigFrequenciesStartIndex_noBig() {
-        assertEquals(
-                4, KernelCpuThreadReader.FrequencyBucketCreator.getBigFrequenciesStartIndex(
-                        new long[]{1, 2, 3, 4}));
+    public void testBucketSetup_moreClustersThanBuckets() {
+        long[] frequencies = {1, 1, 1, 1, 1, 1, 1, 1};
+        KernelCpuThreadReader.FrequencyBucketCreator frequencyBucketCreator =
+                new KernelCpuThreadReader.FrequencyBucketCreator(frequencies, 4);
+        assertArrayEquals(
+                new int[] {1, 1, 1, 1}, frequencyBucketCreator.bucketFrequencies(frequencies));
+        assertArrayEquals(
+                new int[] {1, 1, 1, 5},
+                frequencyBucketCreator.bucketValues(new long[] {1, 1, 1, 1, 1, 1, 1, 1}));
     }
 
     @Test
diff --git a/core/tests/featureflagtests/Android.bp b/core/tests/featureflagtests/Android.bp
new file mode 100644
index 0000000..8730b70
--- /dev/null
+++ b/core/tests/featureflagtests/Android.bp
@@ -0,0 +1,19 @@
+android_test {
+    name: "FrameworksCoreFeatureFlagTests",
+    // We only want this apk build for tests.
+    // Include all test java files.
+    srcs: ["src/**/*.java"],
+    dxflags: ["--core-library"],
+    static_libs: [
+        "android-common",
+        "frameworks-core-util-lib",
+        "androidx.test.rules",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: ["device-tests"],
+}
diff --git a/core/tests/featureflagtests/Android.mk b/core/tests/featureflagtests/Android.mk
deleted file mode 100644
index ce7cb18..0000000
--- a/core/tests/featureflagtests/Android.mk
+++ /dev/null
@@ -1,20 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-# We only want this apk build for tests.
-LOCAL_MODULE_TAGS := tests
-
-# Include all test java files.
-LOCAL_SRC_FILES := \
-    $(call all-java-files-under, src)
-
-LOCAL_DX_FLAGS := --core-library
-LOCAL_STATIC_JAVA_LIBRARIES := android-common frameworks-core-util-lib androidx.test.rules
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
-LOCAL_PACKAGE_NAME := FrameworksCoreFeatureFlagTests
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_CERTIFICATE := platform
-LOCAL_COMPATIBILITY_SUITE := device-tests
-
-include $(BUILD_PACKAGE)
diff --git a/core/tests/overlaytests/device/Android.mk b/core/tests/overlaytests/device/Android.mk
index 5630749..c6d2a51 100644
--- a/core/tests/overlaytests/device/Android.mk
+++ b/core/tests/overlaytests/device/Android.mk
@@ -21,7 +21,7 @@
 LOCAL_PRIVATE_PLATFORM_APIS := true
 LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
 LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_TARGET_REQUIRED_MODULES := \
+LOCAL_REQUIRED_MODULES := \
     OverlayDeviceTests_AppOverlayOne \
     OverlayDeviceTests_AppOverlayTwo \
     OverlayDeviceTests_FrameworkOverlay
diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.mk b/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.mk
index 97a3d00..fa15241 100644
--- a/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.mk
+++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.mk
@@ -19,7 +19,6 @@
 LOCAL_PACKAGE_NAME := OverlayDeviceTests_AppOverlayOne
 LOCAL_SDK_VERSION := current
 LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
 LOCAL_USE_AAPT2 := true
 LOCAL_AAPT_FLAGS := --no-resource-removal
 include $(BUILD_PACKAGE)
diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml b/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml
index 8ac6953..7d28408 100644
--- a/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml
+++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/AndroidManifest.xml
@@ -19,5 +19,5 @@
         android:versionCode="1"
         android:versionName="1.0">
         <application android:hasCode="false" />
-        <overlay android:targetPackage="com.android.overlaytest" android:priority="1" />
+        <overlay android:targetPackage="com.android.overlaytest" />
 </manifest>
diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.mk b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.mk
index a347025..ada9b3c 100644
--- a/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.mk
+++ b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.mk
@@ -19,7 +19,6 @@
 LOCAL_PACKAGE_NAME := OverlayDeviceTests_AppOverlayTwo
 LOCAL_SDK_VERSION := current
 LOCAL_COMPATIBILITY_SUITE := device-tests
-LOCAL_CERTIFICATE := platform
 LOCAL_USE_AAPT2 := true
 LOCAL_AAPT_FLAGS := --no-resource-removal
 include $(BUILD_PACKAGE)
diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayTwo/AndroidManifest.xml b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/AndroidManifest.xml
index f3c39cc..6e75a350 100644
--- a/core/tests/overlaytests/device/test-apps/AppOverlayTwo/AndroidManifest.xml
+++ b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/AndroidManifest.xml
@@ -19,5 +19,5 @@
         android:versionCode="1"
         android:versionName="1.0">
         <application android:hasCode="false" />
-        <overlay android:targetPackage="com.android.overlaytest" android:priority="2" />
+        <overlay android:targetPackage="com.android.overlaytest" />
 </manifest>
diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk
index b48a46b..e7348d5 100644
--- a/core/tests/overlaytests/host/Android.mk
+++ b/core/tests/overlaytests/host/Android.mk
@@ -21,7 +21,7 @@
 LOCAL_JAVA_LIBRARIES := tradefed
 LOCAL_COMPATIBILITY_SUITE := general-tests
 LOCAL_TARGET_REQUIRED_MODULES := \
-    OverlayHostTests_BadSignatureOverlay \
+    OverlayHostTests_NonPlatformSignatureOverlay \
     OverlayHostTests_PlatformSignatureStaticOverlay \
     OverlayHostTests_PlatformSignatureOverlay \
     OverlayHostTests_UpdateOverlay \
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 f9672d2..99b6421 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
@@ -67,10 +67,10 @@
     }
 
     @Test
-    public void failToInstallNonPlatformSignedOverlay() throws Exception {
+    public void failToInstallNonPlatformSignedOverlayTargetPreQ() throws Exception {
         try {
-            installPackage("OverlayHostTests_BadSignatureOverlay.apk");
-            fail("installed a non-platform signed overlay");
+            installPackage("OverlayHostTests_NonPlatformSignatureOverlay.apk");
+            fail("installed a non-platform signed overlay with targetSdkVersion < Q");
         } catch (Exception e) {
             // Expected.
         }
@@ -155,9 +155,17 @@
         }
     }
 
+    @Test
+    public void instantAppsNotVisibleToOMS() throws Exception {
+        installInstantPackage("OverlayHostTests_AppOverlayV1.apk");
+        assertFalse(overlayManagerContainsPackage(APP_OVERLAY_PACKAGE_NAME));
+        installConvertExistingInstantPackageToFull(APP_OVERLAY_PACKAGE_NAME);
+        assertTrue(overlayManagerContainsPackage(APP_OVERLAY_PACKAGE_NAME));
+    }
+
     private void delay() {
         try {
-            Thread.sleep(100);
+            Thread.sleep(1000);
         } catch (InterruptedException e) {
         }
     }
@@ -167,6 +175,15 @@
         delay();
     }
 
+    private void installInstantPackage(String pkg) throws Exception {
+        super.installPackage(pkg, "--instant");
+        delay();
+    }
+
+    private void installConvertExistingInstantPackageToFull(String pkg) throws Exception {
+        getDevice().executeShellCommand("cmd package install-existing --wait --full " + pkg);
+    }
+
     private void setPackageEnabled(String pkg, boolean enabled) throws Exception {
         getDevice().executeShellCommand("cmd package " + (enabled ? "enable " : "disable ") + pkg);
         delay();
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
index 3d2410d..cc7704b 100644
--- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
@@ -18,7 +18,7 @@
 
 include $(CLEAR_VARS)
 LOCAL_MODULE_TAGS := tests
-LOCAL_PACKAGE_NAME := OverlayHostTests_BadSignatureOverlay
+LOCAL_PACKAGE_NAME := OverlayHostTests_NonPlatformSignatureOverlay
 LOCAL_SDK_VERSION := current
 LOCAL_COMPATIBILITY_SUITE := general-tests
 LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml
index 26b3875..67592f8 100644
--- a/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml
@@ -16,6 +16,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.server.om.hosttest.signature_overlay">
+    <uses-sdk android:targetSdkVersion="28" />
     <application android:hasCode="false" />
     <overlay android:targetPackage="android" />
 </manifest>
diff --git a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
index c7b2dd1..f8607f4 100644
--- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/Android.mk
@@ -58,7 +58,6 @@
 LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV1
 LOCAL_SDK_VERSION := current
 LOCAL_COMPATIBILITY_SUITE := general-tests
-LOCAL_CERTIFICATE := platform
 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
@@ -70,7 +69,6 @@
 LOCAL_PACKAGE_NAME := OverlayHostTests_AppOverlayV2
 LOCAL_SDK_VERSION := current
 LOCAL_COMPATIBILITY_SUITE := general-tests
-LOCAL_CERTIFICATE := platform
 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/host/test-apps/UpdateOverlay/app/v2/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/AndroidManifest.xml
index f1a3981..b6ff0c3 100644
--- a/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/AndroidManifest.xml
+++ b/core/tests/overlaytests/host/test-apps/UpdateOverlay/app/v2/AndroidManifest.xml
@@ -17,6 +17,5 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.server.om.hosttest.app_overlay">
     <application android:hasCode="false" />
-    <overlay android:targetPackage="com.android.server.om.hosttest.update_overlay_test"
-        android:category="android.theme" />
+    <overlay android:targetPackage="com.android.server.om.hosttest.update_overlay_test" />
 </manifest>
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 626bbf00..072beae 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -100,36 +100,6 @@
         <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
     </family>
 
-    <family name="arbutus-slab">
-        <font weight="400" style="normal">ArbutusSlab-Regular.ttf</font>
-    </family>
-
-    <family name="arvo">
-        <font weight="400" style="normal">Arvo-Regular.ttf</font>
-        <font weight="400" style="italic">Arvo-Italic.ttf</font>
-        <font weight="700" style="normal">Arvo-Bold.ttf</font>
-        <font weight="700" style="italic">Arvo-BoldItalic.ttf</font>
-    </family>
-    <alias name="arvo-bold" to="arvo" weight="700" />
-
-    <family name="lato">
-        <font weight="400" style="normal">Lato-Regular.ttf</font>
-        <font weight="400" style="italic">Lato-Italic.ttf</font>
-        <font weight="700" style="normal">Lato-Bold.ttf</font>
-        <font weight="700" style="italic">Lato-BoldItalic.ttf</font>
-    </family>
-    <alias name="lato-bold" to="lato" weight="700" />
-
-    <family name="rubik">
-        <font weight="400" style="normal">Rubik-Regular.ttf</font>
-        <font weight="400" style="italic">Rubik-Italic.ttf</font>
-        <font weight="500" style="normal">Rubik-Medium.ttf</font>
-        <font weight="500" style="italic">Rubik-MediumItalic.ttf</font>
-        <font weight="700" style="normal">Rubik-Bold.ttf</font>
-        <font weight="700" style="italic">Rubik-BoldItalic.ttf</font>
-    </family>
-    <alias name="rubik-medium" to="rubik" weight="500" />
-
     <family name="source-sans-pro">
         <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
         <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 365be10..21609d3 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -75,6 +75,7 @@
 const char* AssetManager::VENDOR_OVERLAY_DIR = "/vendor/overlay";
 const char* AssetManager::PRODUCT_OVERLAY_DIR = "/product/overlay";
 const char* AssetManager::PRODUCT_SERVICES_OVERLAY_DIR = "/product_services/overlay";
+const char* AssetManager::ODM_OVERLAY_DIR = "/odm/overlay";
 const char* AssetManager::OVERLAY_THEME_DIR_PROPERTY = "ro.boot.vendor.overlay.theme";
 const char* AssetManager::TARGET_PACKAGE_NAME = "android";
 const char* AssetManager::TARGET_APK_PATH = "/system/framework/framework-res.apk";
diff --git a/libs/androidfw/CursorWindow.cpp b/libs/androidfw/CursorWindow.cpp
index a99e77f..e1067fc 100644
--- a/libs/androidfw/CursorWindow.cpp
+++ b/libs/androidfw/CursorWindow.cpp
@@ -49,15 +49,21 @@
     int ashmemFd = ashmem_create_region(ashmemName.string(), size);
     if (ashmemFd < 0) {
         result = -errno;
+        ALOGE("CursorWindow: ashmem_create_region() failed: errno=%d.", errno);
     } else {
         result = ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE);
-        if (result >= 0) {
+        if (result < 0) {
+            ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d",errno);
+        } else {
             void* data = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0);
             if (data == MAP_FAILED) {
                 result = -errno;
+                ALOGE("CursorWindow: mmap() failed: errno=%d.", errno);
             } else {
                 result = ashmem_set_prot_region(ashmemFd, PROT_READ);
-                if (result >= 0) {
+                if (result < 0) {
+                    ALOGE("CursorWindow: ashmem_set_prot_region() failed: errno=%d.", errno);
+                } else {
                     CursorWindow* window = new CursorWindow(name, ashmemFd,
                             data, size, false /*readOnly*/);
                     result = window->clear();
@@ -86,26 +92,34 @@
     String8 name = parcel->readString8();
 
     status_t result;
+    int actualSize;
     int ashmemFd = parcel->readFileDescriptor();
     if (ashmemFd == int(BAD_TYPE)) {
         result = BAD_TYPE;
+        ALOGE("CursorWindow: readFileDescriptor() failed");
     } else {
         ssize_t size = ashmem_get_size_region(ashmemFd);
         if (size < 0) {
             result = UNKNOWN_ERROR;
+            ALOGE("CursorWindow: ashmem_get_size_region() failed: errno=%d.", errno);
         } else {
             int dupAshmemFd = ::fcntl(ashmemFd, F_DUPFD_CLOEXEC, 0);
             if (dupAshmemFd < 0) {
                 result = -errno;
+                ALOGE("CursorWindow: fcntl() failed: errno=%d.", errno);
             } else {
                 // the size of the ashmem descriptor can be modified between ashmem_get_size_region
                 // call and mmap, so we'll check again immediately after memory is mapped
                 void* data = ::mmap(NULL, size, PROT_READ, MAP_SHARED, dupAshmemFd, 0);
                 if (data == MAP_FAILED) {
                     result = -errno;
-                } else if (ashmem_get_size_region(dupAshmemFd) != size) {
+                    ALOGE("CursorWindow: mmap() failed: errno=%d.", errno);
+                } else if ((actualSize = ashmem_get_size_region(dupAshmemFd)) != size) {
                     ::munmap(data, size);
                     result = BAD_VALUE;
+                    ALOGE("CursorWindow: ashmem_get_size_region() returned %d, expected %d"
+                            " errno=%d",
+                            actualSize, (int) size, errno);
                 } else {
                     CursorWindow* window = new CursorWindow(name, dupAshmemFd,
                             data, size, true /*readOnly*/);
diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS
index f1903a5..87b1467 100644
--- a/libs/androidfw/OWNERS
+++ b/libs/androidfw/OWNERS
@@ -1,3 +1,5 @@
 set noparent
 toddke@google.com
-rtmitchell@google.com
\ No newline at end of file
+rtmitchell@google.com
+
+per-file CursorWindow.cpp=omakoto@google.com
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h
index e22e2d2..a015eab 100644
--- a/libs/androidfw/include/androidfw/AssetManager.h
+++ b/libs/androidfw/include/androidfw/AssetManager.h
@@ -62,6 +62,7 @@
     static const char* VENDOR_OVERLAY_DIR;
     static const char* PRODUCT_OVERLAY_DIR;
     static const char* PRODUCT_SERVICES_OVERLAY_DIR;
+    static const char* ODM_OVERLAY_DIR;
     /*
      * If OVERLAY_THEME_DIR_PROPERTY is set, search for runtime resource overlay
      * APKs in VENDOR_OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index 6a35920..badffd1 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -77,15 +77,14 @@
     })
     public @interface Capability {}
 
-    /**
-     * @hide
-     */
+    /** @hide */
     public static final long INVALID_CAPABILITIES = -1;
 
     /** A bitmask of supported GNSS capabilities. */
     private final long mGnssCapabilities;
 
-    static GnssCapabilities of(long gnssCapabilities) {
+    /** @hide */
+    public static GnssCapabilities of(long gnssCapabilities) {
         return new GnssCapabilities(gnssCapabilities);
     }
 
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index bf1063d..55f1911 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -388,9 +388,10 @@
      */
     public static final int FLAG_NO_SYSTEM_CAPTURE = 0x1 << 12;
 
-    private final static int FLAG_ALL = FLAG_AUDIBILITY_ENFORCED | FLAG_SECURE | FLAG_SCO |
-            FLAG_BEACON | FLAG_HW_AV_SYNC | FLAG_HW_HOTWORD | FLAG_BYPASS_INTERRUPTION_POLICY |
-            FLAG_BYPASS_MUTE | FLAG_LOW_LATENCY | FLAG_DEEP_BUFFER | FLAG_MUTE_HAPTIC;
+    private static final int FLAG_ALL = FLAG_AUDIBILITY_ENFORCED | FLAG_SECURE | FLAG_SCO
+            | FLAG_BEACON | FLAG_HW_AV_SYNC | FLAG_HW_HOTWORD | FLAG_BYPASS_INTERRUPTION_POLICY
+            | FLAG_BYPASS_MUTE | FLAG_LOW_LATENCY | FLAG_DEEP_BUFFER | FLAG_NO_MEDIA_PROJECTION
+            | FLAG_MUTE_HAPTIC | FLAG_NO_SYSTEM_CAPTURE;
     private final static int FLAG_ALL_PUBLIC = FLAG_AUDIBILITY_ENFORCED |
             FLAG_HW_AV_SYNC | FLAG_LOW_LATENCY;
 
@@ -557,7 +558,7 @@
         private int mContentType = CONTENT_TYPE_UNKNOWN;
         private int mSource = MediaRecorder.AudioSource.AUDIO_SOURCE_INVALID;
         private int mFlags = 0x0;
-        private boolean mMuteHapticChannels = true;
+        private boolean mMuteHapticChannels = false;
         private HashSet<String> mTags = new HashSet<String>();
         private Bundle mBundle;
 
@@ -888,7 +889,7 @@
 
         /**
          * Specifying if haptic should be muted or not when playing audio-haptic coupled data.
-         * By default, haptic channels are disabled.
+         * By default, haptic channels are enabled.
          * @param muted true to force muting haptic channels.
          * @return the same Builder instance.
          */
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 5d12c3c..b2ebfa9 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -43,8 +43,6 @@
     /** @hide */
     public static final int PLAYER_PIID_INVALID = -1;
     /** @hide */
-    public static final int PLAYER_PIID_UNASSIGNED = 0;
-    /** @hide */
     public static final int PLAYER_UPID_INVALID = -1;
 
     // information about the implementation
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index 50caf733..ee8f1b3 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -50,6 +50,10 @@
     private static final boolean DEBUG = DEBUG_APP_OPS || false;
     private static IAudioService sService; //lazy initialization, use getService()
 
+    /** if true, only use OP_PLAY_AUDIO monitoring for logging, and rely on muting to happen
+     *  in AudioFlinger */
+    private static final boolean USE_AUDIOFLINGER_MUTING_FOR_OP = true;
+
     // parameters of the player that affect AppOps
     protected AudioAttributes mAttributes;
 
@@ -67,13 +71,13 @@
 
     // for AppOps
     private @Nullable IAppOpsService mAppOps;
-    private IAppOpsCallback mAppOpsCallback;
+    private @Nullable IAppOpsCallback mAppOpsCallback;
     @GuardedBy("mLock")
     private boolean mHasAppOpsPlayAudio = true;
 
     private final int mImplType;
     // uniquely identifies the Player Interface throughout the system (P I Id)
-    private int mPlayerIId = AudioPlaybackConfiguration.PLAYER_PIID_UNASSIGNED;
+    private int mPlayerIId = AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
 
     @GuardedBy("mLock")
     private int mState;
@@ -104,27 +108,27 @@
      * Call from derived class when instantiation / initialization is successful
      */
     protected void baseRegisterPlayer() {
-        int newPiid = AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
-        IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
-        mAppOps = IAppOpsService.Stub.asInterface(b);
-        // initialize mHasAppOpsPlayAudio
-        updateAppOpsPlayAudio();
-        // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
-        mAppOpsCallback = new IAppOpsCallbackWrapper(this);
-        try {
-            mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO,
-                    ActivityThread.currentPackageName(), mAppOpsCallback);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error registering appOps callback", e);
-            mHasAppOpsPlayAudio = false;
+        if (!USE_AUDIOFLINGER_MUTING_FOR_OP) {
+            IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+            mAppOps = IAppOpsService.Stub.asInterface(b);
+            // initialize mHasAppOpsPlayAudio
+            updateAppOpsPlayAudio();
+            // register a callback to monitor whether the OP_PLAY_AUDIO is still allowed
+            mAppOpsCallback = new IAppOpsCallbackWrapper(this);
+            try {
+                mAppOps.startWatchingMode(AppOpsManager.OP_PLAY_AUDIO,
+                        ActivityThread.currentPackageName(), mAppOpsCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error registering appOps callback", e);
+                mHasAppOpsPlayAudio = false;
+            }
         }
         try {
-            newPiid = getService().trackPlayer(
+            mPlayerIId = getService().trackPlayer(
                     new PlayerIdCard(mImplType, mAttributes, new IPlayerWrapper(this)));
         } catch (RemoteException e) {
             Log.e(TAG, "Error talking to audio service, player will not be tracked", e);
         }
-        mPlayerIId = newPiid;
     }
 
     /**
@@ -284,6 +288,9 @@
      * Must be called synchronized on mLock.
      */
     void updateAppOpsPlayAudio_sync(boolean attributesChanged) {
+        if (USE_AUDIOFLINGER_MUTING_FOR_OP) {
+            return;
+        }
         boolean oldHasAppOpsPlayAudio = mHasAppOpsPlayAudio;
         try {
             int mode = AppOpsManager.MODE_IGNORED;
@@ -333,6 +340,9 @@
      * @return
      */
     boolean isRestricted_sync() {
+        if (USE_AUDIOFLINGER_MUTING_FOR_OP) {
+            return false;
+        }
         // check app ops
         if (mHasAppOpsPlayAudio) {
             return false;
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 65b58ab..5c3d780 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -129,6 +129,7 @@
         // NDK or LLNDK or NDK-compliant
         "libandroid",
         "libbinder_ndk",
+        "libcgrouprc",
         "libmediandk",
         "libmediametrics",
         "libnativehelper_compat_libc++",
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index 1f2480b..177f2b8 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -248,16 +248,21 @@
     ASystemFontIterator_open; # introduced=29
     ASystemFontIterator_close; # introduced=29
     ASystemFontIterator_next; # introduced=29
-    ASystemFont_close; # introduced=29
-    ASystemFont_getFontFilePath; # introduced=29
-    ASystemFont_getWeight; # introduced=29
-    ASystemFont_isItalic; # introduced=29
-    ASystemFont_getLocale; # introduced=29
-    ASystemFont_getCollectionIndex; # introduced=29
-    ASystemFont_getAxisCount; # introduced=29
-    ASystemFont_getAxisTag; # introduced=29
-    ASystemFont_getAxisValue; # introduced=29
-    ASystemFont_matchFamilyStyleCharacter; # introduced=29
+    AFont_close; # introduced=29
+    AFont_getFontFilePath; # introduced=29
+    AFont_getWeight; # introduced=29
+    AFont_isItalic; # introduced=29
+    AFont_getLocale; # introduced=29
+    AFont_getCollectionIndex; # introduced=29
+    AFont_getAxisCount; # introduced=29
+    AFont_getAxisTag; # introduced=29
+    AFont_getAxisValue; # introduced=29
+    AFontMatcher_create; # introduced=29
+    AFontMatcher_destroy; # introduced=29
+    AFontMatcher_setStyle; # introduced=29
+    AFontMatcher_setLocales; # introduced=29
+    AFontMatcher_setFamilyVariant; # introduced=29
+    AFontMatcher_match; # introduced=29
     ATrace_beginSection; # introduced=23
     ATrace_endSection; # introduced=23
     ATrace_isEnabled; # introduced=23
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 4d3d1d6..302cbd1 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -16,6 +16,8 @@
 
 #include <jni.h>
 
+#include <android/font.h>
+#include <android/font_matcher.h>
 #include <android/system_fonts.h>
 
 #include <memory>
@@ -53,7 +55,7 @@
     XmlDocUniquePtr mCustomizationXmlDoc;
 };
 
-struct ASystemFont {
+struct AFont {
     std::string mFilePath;
     std::unique_ptr<std::string> mLocale;
     uint16_t mWeight;
@@ -62,6 +64,19 @@
     std::vector<std::pair<uint32_t, float>> mAxes;
 };
 
+struct AFontMatcher {
+    minikin::FontStyle mFontStyle;
+    uint32_t mLocaleListId = 0;  // 0 is reserved for empty locale ID.
+    bool mFamilyVariant = AFAMILY_VARIANT_DEFAULT;
+};
+
+static_assert(static_cast<uint32_t>(AFAMILY_VARIANT_DEFAULT) ==
+              static_cast<uint32_t>(minikin::FamilyVariant::DEFAULT));
+static_assert(static_cast<uint32_t>(AFAMILY_VARIANT_COMPACT) ==
+              static_cast<uint32_t>(minikin::FamilyVariant::COMPACT));
+static_assert(static_cast<uint32_t>(AFAMILY_VARIANT_ELEGANT) ==
+              static_cast<uint32_t>(minikin::FamilyVariant::ELEGANT));
+
 namespace {
 
 std::string xmlTrim(const std::string& in) {
@@ -101,7 +116,7 @@
     return nullptr;
 }
 
-void copyFont(const XmlDocUniquePtr& xmlDoc, xmlNode* fontNode, ASystemFont* out,
+void copyFont(const XmlDocUniquePtr& xmlDoc, xmlNode* fontNode, AFont* out,
               const std::string& pathPrefix) {
     const xmlChar* LOCALE_ATTR_NAME = BAD_CAST("lang");
     XmlCharUniquePtr filePathStr(
@@ -197,24 +212,48 @@
     delete ite;
 }
 
-ASystemFont* ASystemFont_matchFamilyStyleCharacter(
-        const char* _Nonnull familyName,
+AFontMatcher* _Nonnull AFontMatcher_create() {
+    return new AFontMatcher();
+}
+
+void AFontMatcher_destroy(AFontMatcher* matcher) {
+    delete matcher;
+}
+
+void AFontMatcher_setStyle(
+        AFontMatcher* _Nonnull matcher,
         uint16_t weight,
-        bool italic,
-        const char* _Nonnull languageTags,
+        bool italic) {
+    matcher->mFontStyle = minikin::FontStyle(
+            weight, static_cast<minikin::FontStyle::Slant>(italic));
+}
+
+void AFontMatcher_setLocales(
+        AFontMatcher* _Nonnull matcher,
+        const char* _Nonnull languageTags) {
+    matcher->mLocaleListId = minikin::registerLocaleList(languageTags);
+}
+
+void AFontMatcher_setFamilyVariant(AFontMatcher* _Nonnull matcher, uint32_t familyVariant) {
+    matcher->mFamilyVariant = familyVariant;
+}
+
+AFont* _Nonnull AFontMatcher_match(
+        const AFontMatcher* _Nonnull matcher,
+        const char* _Nonnull familyName,
         const uint16_t* _Nonnull text,
-        uint32_t textLength,
+        const uint32_t textLength,
         uint32_t* _Nullable runLength) {
     std::shared_ptr<minikin::FontCollection> fc =
             minikin::SystemFonts::findFontCollection(familyName);
-    std::vector<minikin::FontCollection::Run> runs =
-            fc->itemize(minikin::U16StringPiece(text, textLength),
-                        minikin::FontStyle(weight, static_cast<minikin::FontStyle::Slant>(italic)),
-                        minikin::registerLocaleList(languageTags),
-                        minikin::FamilyVariant::DEFAULT);
+    std::vector<minikin::FontCollection::Run> runs = fc->itemize(
+                minikin::U16StringPiece(text, textLength),
+                matcher->mFontStyle,
+                matcher->mLocaleListId,
+                static_cast<minikin::FamilyVariant>(matcher->mFamilyVariant));
 
     const minikin::Font* font = runs[0].fakedFont.font;
-    std::unique_ptr<ASystemFont> result = std::make_unique<ASystemFont>();
+    std::unique_ptr<AFont> result = std::make_unique<AFont>();
     const android::MinikinFontSkia* minikinFontSkia =
             reinterpret_cast<android::MinikinFontSkia*>(font->typeface().get());
     result->mFilePath = minikinFontSkia->getFilePath();
@@ -253,7 +292,7 @@
     }
 }
 
-ASystemFont* ASystemFontIterator_next(ASystemFontIterator* ite) {
+AFont* ASystemFontIterator_next(ASystemFontIterator* ite) {
     LOG_ALWAYS_FATAL_IF(ite == nullptr, "nullptr has passed as iterator argument");
     if (ite->mXmlDoc) {
         ite->mFontNode = findNextFontNode(ite->mXmlDoc, ite->mFontNode);
@@ -262,7 +301,7 @@
             ite->mXmlDoc.reset();
             ite->mFontNode = nullptr;
         } else {
-            std::unique_ptr<ASystemFont> font = std::make_unique<ASystemFont>();
+            std::unique_ptr<AFont> font = std::make_unique<AFont>();
             copyFont(ite->mXmlDoc, ite->mFontNode, font.get(), "/system/fonts/");
             if (!isFontFileAvailable(font->mFilePath)) {
                 return ASystemFontIterator_next(ite);
@@ -279,7 +318,7 @@
             ite->mFontNode = nullptr;
             return nullptr;
         } else {
-            std::unique_ptr<ASystemFont> font = std::make_unique<ASystemFont>();
+            std::unique_ptr<AFont> font = std::make_unique<AFont>();
             copyFont(ite->mCustomizationXmlDoc, ite->mFontNode, font.get(), "/product/fonts/");
             if (!isFontFileAvailable(font->mFilePath)) {
                 return ASystemFontIterator_next(ite);
@@ -290,48 +329,48 @@
     return nullptr;
 }
 
-void ASystemFont_close(ASystemFont* font) {
+void AFont_close(AFont* font) {
     delete font;
 }
 
-const char* ASystemFont_getFontFilePath(const ASystemFont* font) {
+const char* AFont_getFontFilePath(const AFont* font) {
     LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed as font argument");
     return font->mFilePath.c_str();
 }
 
-uint16_t ASystemFont_getWeight(const ASystemFont* font) {
+uint16_t AFont_getWeight(const AFont* font) {
     LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed as font argument");
     return font->mWeight;
 }
 
-bool ASystemFont_isItalic(const ASystemFont* font) {
+bool AFont_isItalic(const AFont* font) {
     LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed as font argument");
     return font->mItalic;
 }
 
-const char* ASystemFont_getLocale(const ASystemFont* font) {
+const char* AFont_getLocale(const AFont* font) {
     LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed to font argument");
     return font->mLocale ? nullptr : font->mLocale->c_str();
 }
 
-size_t ASystemFont_getCollectionIndex(const ASystemFont* font) {
+size_t AFont_getCollectionIndex(const AFont* font) {
     LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed to font argument");
     return font->mCollectionIndex;
 }
 
-size_t ASystemFont_getAxisCount(const ASystemFont* font) {
+size_t AFont_getAxisCount(const AFont* font) {
     LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed to font argument");
     return font->mAxes.size();
 }
 
-uint32_t ASystemFont_getAxisTag(const ASystemFont* font, uint32_t axisIndex) {
+uint32_t AFont_getAxisTag(const AFont* font, uint32_t axisIndex) {
     LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed to font argument");
     LOG_ALWAYS_FATAL_IF(axisIndex >= font->mAxes.size(),
                         "given axis index is out of bounds. (< %zd", font->mAxes.size());
     return font->mAxes[axisIndex].first;
 }
 
-float ASystemFont_getAxisValue(const ASystemFont* font, uint32_t axisIndex) {
+float AFont_getAxisValue(const AFont* font, uint32_t axisIndex) {
     LOG_ALWAYS_FATAL_IF(font == nullptr, "nullptr has passed to font argument");
     LOG_ALWAYS_FATAL_IF(axisIndex >= font->mAxes.size(),
                         "given axis index is out of bounds. (< %zd", font->mAxes.size());
diff --git a/packages/CaptivePortalLogin/Android.bp b/packages/CaptivePortalLogin/Android.bp
index bc614e9..a345091 100644
--- a/packages/CaptivePortalLogin/Android.bp
+++ b/packages/CaptivePortalLogin/Android.bp
@@ -23,6 +23,7 @@
     static_libs: [
         "androidx.legacy_legacy-support-v4",
         "metrics-constants-protos",
+        "captiveportal-lib",
     ],
     manifest: "AndroidManifest.xml",
 }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynandroid/BootCompletedReceiver.java b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/BootCompletedReceiver.java
index 38576ee..ea7b378 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynandroid/BootCompletedReceiver.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/BootCompletedReceiver.java
@@ -19,8 +19,10 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.image.DynamicSystemClient;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 
 
@@ -35,6 +37,10 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
+        if (!featureFlagEnabled()) {
+            return;
+        }
+
         String action = intent.getAction();
 
         Log.d(TAG, "Broadcast received: " + action);
@@ -47,4 +53,9 @@
             context.startServiceAsUser(startServiceIntent, UserHandle.SYSTEM);
         }
     }
+
+    private boolean featureFlagEnabled() {
+        return SystemProperties.getBoolean(
+                FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.DYNAMIC_SYSTEM, false);
+    }
 }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynandroid/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/DynamicSystemInstallationService.java
index 2ad72eb..5c6885a 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynandroid/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/DynamicSystemInstallationService.java
@@ -257,7 +257,7 @@
             return;
         }
 
-        if (getStatus() != STATUS_READY) {
+        if (!isDynamicSystemInstalled() && (getStatus() != STATUS_READY)) {
             Log.e(TAG, "Trying to discard AOT while there is no complete installation");
             return;
         }
@@ -273,13 +273,25 @@
     }
 
     private void executeRebootToDynSystemCommand() {
-        if (mInstallTask == null || mInstallTask.getStatus() != FINISHED) {
+        boolean enabled = false;
+
+        if (mInstallTask != null && mInstallTask.getStatus() == FINISHED) {
+            enabled = mInstallTask.commit();
+        } else if (isDynamicSystemInstalled()) {
+            enabled = mDynSystem.setEnable(true);
+        } else {
             Log.e(TAG, "Trying to reboot to AOT while there is no complete installation");
             return;
         }
 
-        if (!mInstallTask.commit()) {
-            Log.e(TAG, "Failed to commit installation because of native runtime error.");
+        if (enabled) {
+            PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+
+            if (powerManager != null) {
+                powerManager.reboot("dynsystem");
+            }
+        } else {
+            Log.e(TAG, "Failed to enable DynamicSystem because of native runtime error.");
             mNM.cancel(NOTIFICATION_ID);
 
             Toast.makeText(this,
@@ -287,14 +299,6 @@
                     Toast.LENGTH_LONG).show();
 
             mDynSystem.remove();
-
-            return;
-        }
-
-        PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
-
-        if (powerManager != null) {
-            powerManager.reboot("dynsystem");
         }
     }
 
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynandroid/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/VerificationActivity.java
index 269645d..b1c09381 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynandroid/VerificationActivity.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynandroid/VerificationActivity.java
@@ -25,8 +25,10 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.image.DynamicSystemClient;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 
 
@@ -49,6 +51,12 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        if (!featureFlagEnabled()) {
+            Log.w(TAG, FeatureFlagUtils.DYNAMIC_SYSTEM + " not enabled; activity aborted.");
+            finish();
+            return;
+        }
+
         KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
 
         if (km != null) {
@@ -98,6 +106,11 @@
         startServiceAsUser(intent, UserHandle.SYSTEM);
     }
 
+    private boolean featureFlagEnabled() {
+        return SystemProperties.getBoolean(
+                FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.DYNAMIC_SYSTEM, false);
+    }
+
     static boolean isVerified(String url) {
         return sVerifiedUrl != null && sVerifiedUrl.equals(url);
     }
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index d56f97f..57a3db5 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -14,6 +14,15 @@
 // limitations under the License.
 //
 
+java_library {
+    name: "captiveportal-lib",
+    srcs: ["common/**/*.java"],
+    libs: [
+        "androidx.annotation_annotation",
+    ],
+    sdk_version: "system_current",
+}
+
 java_defaults {
     name: "NetworkStackCommon",
     sdk_version: "system_current",
@@ -35,6 +44,7 @@
         "networkstack-aidl-interfaces-java",
         "datastallprotosnano",
         "networkstackprotosnano",
+        "captiveportal-lib",
     ],
     manifest: "AndroidManifestBase.xml",
 }
diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java b/packages/NetworkStack/common/CaptivePortalProbeResult.java
similarity index 94%
rename from core/java/android/net/captiveportal/CaptivePortalProbeResult.java
rename to packages/NetworkStack/common/CaptivePortalProbeResult.java
index a1d3de24..48cd48b 100644
--- a/core/java/android/net/captiveportal/CaptivePortalProbeResult.java
+++ b/packages/NetworkStack/common/CaptivePortalProbeResult.java
@@ -16,17 +16,13 @@
 
 package android.net.captiveportal;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 /**
  * Result of calling isCaptivePortal().
  * @hide
  */
-@SystemApi
-@TestApi
 public final class CaptivePortalProbeResult {
     public static final int SUCCESS_CODE = 204;
     public static final int FAILED_CODE = 599;
diff --git a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java b/packages/NetworkStack/common/CaptivePortalProbeSpec.java
similarity index 95%
rename from core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
rename to packages/NetworkStack/common/CaptivePortalProbeSpec.java
index b354607..bf983a5 100644
--- a/core/java/android/net/captiveportal/CaptivePortalProbeSpec.java
+++ b/packages/NetworkStack/common/CaptivePortalProbeSpec.java
@@ -19,16 +19,12 @@
 import static android.net.captiveportal.CaptivePortalProbeResult.PORTAL_CODE;
 import static android.net.captiveportal.CaptivePortalProbeResult.SUCCESS_CODE;
 
-import static com.android.internal.util.Preconditions.checkNotNull;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -40,8 +36,6 @@
 import java.util.regex.PatternSyntaxException;
 
 /** @hide */
-@SystemApi
-@TestApi
 public abstract class CaptivePortalProbeSpec {
     private static final String TAG = CaptivePortalProbeSpec.class.getSimpleName();
     private static final String REGEX_SEPARATOR = "@@/@@";
@@ -192,4 +186,10 @@
         // No value is a match ("no location header" passes the location rule for non-redirects)
         return pattern == null || TextUtils.isEmpty(value) || pattern.matcher(value).matches();
     }
+
+    // Throws NullPointerException if the input is null.
+    private static <T> T checkNotNull(T object) {
+        if (object == null) throw new NullPointerException();
+        return object;
+    }
 }
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
index 5650f21..bee4bbd9 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
@@ -33,8 +33,8 @@
 import android.net.ipmemorystore.Blob;
 import android.net.ipmemorystore.IOnBlobRetrievedListener;
 import android.net.ipmemorystore.IOnL2KeyResponseListener;
-import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
-import android.net.ipmemorystore.IOnSameNetworkResponseListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
+import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
 import android.net.ipmemorystore.IOnStatusListener;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.NetworkAttributesParcelable;
@@ -297,16 +297,16 @@
      */
     @Override
     public void isSameNetwork(@Nullable final String l2Key1, @Nullable final String l2Key2,
-            @Nullable final IOnSameNetworkResponseListener listener) {
+            @Nullable final IOnSameL3NetworkResponseListener listener) {
         if (null == listener) return;
         mExecutor.execute(() -> {
             try {
                 if (null == l2Key1 || null == l2Key2) {
-                    listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+                    listener.onSameL3NetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
                     return;
                 }
                 if (null == mDb) {
-                    listener.onSameNetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
+                    listener.onSameL3NetworkResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), null);
                     return;
                 }
                 try {
@@ -315,16 +315,16 @@
                     final NetworkAttributes attr2 =
                             IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key2);
                     if (null == attr1 || null == attr2) {
-                        listener.onSameNetworkResponse(makeStatus(SUCCESS),
+                        listener.onSameL3NetworkResponse(makeStatus(SUCCESS),
                                 new SameL3NetworkResponse(l2Key1, l2Key2,
                                         -1f /* never connected */).toParcelable());
                         return;
                     }
                     final float confidence = attr1.getNetworkGroupSamenessConfidence(attr2);
-                    listener.onSameNetworkResponse(makeStatus(SUCCESS),
+                    listener.onSameL3NetworkResponse(makeStatus(SUCCESS),
                             new SameL3NetworkResponse(l2Key1, l2Key2, confidence).toParcelable());
                 } catch (Exception e) {
-                    listener.onSameNetworkResponse(makeStatus(ERROR_GENERIC), null);
+                    listener.onSameL3NetworkResponse(makeStatus(ERROR_GENERIC), null);
                 }
             } catch (final RemoteException e) {
                 // Client at the other end died
@@ -343,7 +343,7 @@
      */
     @Override
     public void retrieveNetworkAttributes(@Nullable final String l2Key,
-            @Nullable final IOnNetworkAttributesRetrieved listener) {
+            @Nullable final IOnNetworkAttributesRetrievedListener listener) {
         if (null == listener) return;
         mExecutor.execute(() -> {
             try {
diff --git a/tests/net/java/android/net/captiveportal/CaptivePortalProbeSpecTest.java b/packages/NetworkStack/tests/src/android/net/captiveportal/CaptivePortalProbeSpecTest.java
similarity index 100%
rename from tests/net/java/android/net/captiveportal/CaptivePortalProbeSpecTest.java
rename to packages/NetworkStack/tests/src/android/net/captiveportal/CaptivePortalProbeSpecTest.java
diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
index 94cc589..a00eff7 100644
--- a/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -31,8 +31,8 @@
 import android.net.ipmemorystore.Blob;
 import android.net.ipmemorystore.IOnBlobRetrievedListener;
 import android.net.ipmemorystore.IOnL2KeyResponseListener;
-import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
-import android.net.ipmemorystore.IOnSameNetworkResponseListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
+import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
 import android.net.ipmemorystore.IOnStatusListener;
 import android.net.ipmemorystore.NetworkAttributes;
 import android.net.ipmemorystore.NetworkAttributesParcelable;
@@ -163,9 +163,9 @@
     private interface OnNetworkAttributesRetrievedListener  {
         void onNetworkAttributesRetrieved(Status status, String l2Key, NetworkAttributes attr);
     }
-    private IOnNetworkAttributesRetrieved onNetworkAttributesRetrieved(
+    private IOnNetworkAttributesRetrievedListener onNetworkAttributesRetrieved(
             final OnNetworkAttributesRetrievedListener functor) {
-        return new IOnNetworkAttributesRetrieved() {
+        return new IOnNetworkAttributesRetrievedListener() {
             @Override
             public void onNetworkAttributesRetrieved(final StatusParcelable status,
                     final String l2Key, final NetworkAttributesParcelable attributes)
@@ -182,17 +182,17 @@
     }
 
     /** Helper method to make an IOnSameNetworkResponseListener */
-    private interface OnSameNetworkResponseListener {
-        void onSameNetworkResponse(Status status, SameL3NetworkResponse answer);
+    private interface OnSameL3NetworkResponseListener {
+        void onSameL3NetworkResponse(Status status, SameL3NetworkResponse answer);
     }
-    private IOnSameNetworkResponseListener onSameResponse(
-            final OnSameNetworkResponseListener functor) {
-        return new IOnSameNetworkResponseListener() {
+    private IOnSameL3NetworkResponseListener onSameResponse(
+            final OnSameL3NetworkResponseListener functor) {
+        return new IOnSameL3NetworkResponseListener() {
             @Override
-            public void onSameNetworkResponse(final StatusParcelable status,
+            public void onSameL3NetworkResponse(final StatusParcelable status,
                     final SameL3NetworkResponseParcelable sameL3Network)
                     throws RemoteException {
-                functor.onSameNetworkResponse(new Status(status),
+                functor.onSameL3NetworkResponse(new Status(status),
                         null == sameL3Network ? null : new SameL3NetworkResponse(sameL3Network));
             }
 
diff --git a/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_chart.xml b/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_chart.xml
index 25246d6..0a336bf 100644
--- a/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_chart.xml
+++ b/packages/SettingsLib/BarChartPreference/res/layout/settings_bar_chart.xml
@@ -36,6 +36,7 @@
         <LinearLayout
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:layout_marginBottom="20dp"
             android:gravity="center|bottom">
 
             <com.android.settingslib.widget.BarView
diff --git a/packages/SettingsLib/BarChartPreference/res/values/dimens.xml b/packages/SettingsLib/BarChartPreference/res/values/dimens.xml
index 7148afa..5f741a0 100644
--- a/packages/SettingsLib/BarChartPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/BarChartPreference/res/values/dimens.xml
@@ -16,6 +16,6 @@
   -->
 
 <resources>
-    <dimen name="settings_bar_view_max_height">106dp</dimen>
+    <dimen name="settings_bar_view_max_height">72dp</dimen>
     <dimen name="settings_bar_view_icon_size">24dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/BarChartPreference/res/values/styles.xml b/packages/SettingsLib/BarChartPreference/res/values/styles.xml
index 8302c01..7a3fb7d 100644
--- a/packages/SettingsLib/BarChartPreference/res/values/styles.xml
+++ b/packages/SettingsLib/BarChartPreference/res/values/styles.xml
@@ -40,7 +40,7 @@
 
     <style name="SettingsBarViewStyle">
         <item name="android:layout_width">0dp</item>
-        <item name="android:layout_height">250dp</item>
+        <item name="android:layout_height">168dp</item>
         <item name="android:layout_weight">1</item>
         <item name="android:layout_marginStart">8dp</item>
         <item name="android:layout_marginEnd">8dp</item>
@@ -63,6 +63,7 @@
         <item name="android:layout_height">@dimen/settings_bar_view_icon_size</item>
         <item name="android:scaleType">fitCenter</item>
         <item name="android:layout_marginTop">12dp</item>
+        <item name="android:tint">?android:attr/textColorPrimary</item>
     </style>
 
     <style name="SettingsBarChartBarTitle">
@@ -78,7 +79,6 @@
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_marginTop">4dp</item>
-        <item name="android:layout_marginBottom">12dp</item>
         <item name="android:singleLine">true</item>
         <item name="android:ellipsize">marquee</item>
         <item name="android:textAppearance">@style/BarChart.Text.Summary</item>
diff --git a/packages/SettingsLib/HelpUtils/res/drawable/ic_help_actionbar.xml b/packages/SettingsLib/HelpUtils/res/drawable/ic_help_actionbar.xml
index 9089a93..1a4b1c3 100644
--- a/packages/SettingsLib/HelpUtils/res/drawable/ic_help_actionbar.xml
+++ b/packages/SettingsLib/HelpUtils/res/drawable/ic_help_actionbar.xml
@@ -20,6 +20,7 @@
         android:height="24dp"
         android:viewportWidth="24.0"
         android:viewportHeight="24.0"
+        android:autoMirrored="true"
         android:tint="?android:attr/colorControlNormal">
     <path
         android:fillColor="#FFFFFFFF"
diff --git a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
index 5108efb..0ffd471 100644
--- a/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/Tile/src/com/android/settingslib/drawer/Tile.java
@@ -33,6 +33,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -299,7 +300,15 @@
             }
         }
         if (iconResId != 0) {
-            return Icon.createWithResource(activityInfo.packageName, iconResId);
+            final Icon icon = Icon.createWithResource(activityInfo.packageName, iconResId);
+            if (isIconTintable(context)) {
+                final TypedArray a = context.obtainStyledAttributes(new int[] {
+                        android.R.attr.colorControlNormal});
+                final int tintColor = a.getColor(0, 0);
+                a.recycle();
+                icon.setTint(tintColor);
+            }
+            return icon;
         } else {
             return null;
         }
@@ -309,16 +318,12 @@
      * Whether the icon can be tinted. This is true when icon needs to be monochrome (single-color)
      */
     public boolean isIconTintable(Context context) {
+        ensureMetadataNotStale(context);
         if (mMetaData != null
                 && mMetaData.containsKey(TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE)) {
             return mMetaData.getBoolean(TileUtils.META_DATA_PREFERENCE_ICON_TINTABLE);
         }
-        ensureMetadataNotStale(context);
-        final String pkgName = context.getPackageName();
-        // If this drawable is coming from outside Settings, tint it to match the color.
-        final ActivityInfo activityInfo = getActivityInfo(context);
-        return activityInfo != null
-                && !TextUtils.equals(pkgName, activityInfo.packageName);
+        return false;
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 148046b..46e9129 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -4,6 +4,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
@@ -148,6 +149,12 @@
             final Uri iconUri = uriString != null ? Uri.parse(uriString) : null;
             if (iconUri != null) {
                 try {
+                    context.getContentResolver().takePersistableUriPermission(iconUri,
+                            Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                } catch (SecurityException e) {
+                    Log.e(TAG, "Failed to take persistable permission for: " + iconUri);
+                }
+                try {
                     final Bitmap bitmap = MediaStore.Images.Media.getBitmap(
                             context.getContentResolver(), iconUri);
                     if (bitmap != null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
index 9feacac..eeb6cb0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerWhitelistBackend.java
@@ -75,20 +75,32 @@
             return true;
         }
 
+        if (isDefaultActiveApp(pkg)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Check if it is default active app in multiple area(i.e. SMS, Dialer, Device admin..)
+     */
+    public boolean isDefaultActiveApp(String pkg) {
         // Additionally, check if pkg is default dialer/sms. They are considered essential apps and
         // should be automatically whitelisted (otherwise user may be able to set restriction on
         // them, leading to bad device behavior.)
-        if (!mAppContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return false;
-        }
+
+        final boolean hasTelephony = mAppContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY);
         final ComponentName defaultSms = SmsApplication.getDefaultSmsApplication(mAppContext,
                 true /* updateIfNeeded */);
-        if (defaultSms != null && TextUtils.equals(pkg, defaultSms.getPackageName())) {
+        if (hasTelephony && defaultSms != null && TextUtils.equals(pkg,
+                defaultSms.getPackageName())) {
             return true;
         }
 
         final String defaultDialer = DefaultDialerManager.getDefaultDialerApplication(mAppContext);
-        if (TextUtils.equals(pkg, defaultDialer)) {
+        if (hasTelephony && TextUtils.equals(pkg, defaultDialer)) {
             return true;
         }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
index b9197fe..66ee802 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
@@ -210,7 +210,6 @@
     private void setupUi(ConditionTag tag, View row) {
         if (tag.lines == null) {
             tag.lines = row.findViewById(android.R.id.content);
-            tag.lines.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
         }
 
         if (tag.line1 == null) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java
index bfda888..d0d1e58 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileTest.java
@@ -116,16 +116,11 @@
     }
 
     @Test
-    public void isIconTintable_noMetadata_shouldReturnPackageNameCheck() {
-        final Tile tile1 = new Tile(mActivityInfo, "category");
-        assertThat(tile1.isIconTintable(RuntimeEnvironment.application)).isFalse();
+    public void isIconTintable_noTintableMetadata_shouldReturnFalse() {
+        final Tile tile = new Tile(mActivityInfo, "category");
+        mActivityInfo.metaData.putInt(META_DATA_PREFERENCE_ICON, android.R.drawable.ic_info);
 
-        final ActivityInfo activityInfo = new ActivityInfo();
-        activityInfo.packageName = "blah";
-        activityInfo.name = "abc";
-
-        final Tile tile2 = new Tile(activityInfo, "category");
-        assertThat(tile2.isIconTintable(RuntimeEnvironment.application)).isTrue();
+        assertThat(tile.isIconTintable(RuntimeEnvironment.application)).isFalse();
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index c892711..aa1ac4e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -205,10 +205,6 @@
                 null /* defaultCategory */, outTiles, false /* usePriority */);
         assertThat(outTiles.size()).isEqualTo(1);
         assertThat(outTiles.get(0).getTitle(mContext)).isEqualTo("my localized title");
-
-        // Icon should be tintable because the tile is not from settings package, and
-        // "forceTintExternalIcon" is set
-        assertThat(outTiles.get(0).isIconTintable(mContext)).isTrue();
     }
 
     @Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
index bbf807d2..44ee423 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerWhitelistBackendTest.java
@@ -118,6 +118,7 @@
         ShadowSmsApplication.setDefaultSmsApplication(new ComponentName(testSms, "receiver"));
 
         assertThat(mPowerWhitelistBackend.isWhitelisted(testSms)).isTrue();
+        assertThat(mPowerWhitelistBackend.isDefaultActiveApp(testSms)).isTrue();
     }
 
     @Test
@@ -126,6 +127,7 @@
         ShadowDefaultDialerManager.setDefaultDialerApplication(testDialer);
 
         assertThat(mPowerWhitelistBackend.isWhitelisted(testDialer)).isTrue();
+        assertThat(mPowerWhitelistBackend.isDefaultActiveApp(testDialer)).isTrue();
     }
 
     @Test
@@ -133,6 +135,7 @@
         doReturn(true).when(mDevicePolicyManager).packageHasActiveAdmins(PACKAGE_ONE);
 
         assertThat(mPowerWhitelistBackend.isWhitelisted(PACKAGE_ONE)).isTrue();
+        assertThat(mPowerWhitelistBackend.isDefaultActiveApp(PACKAGE_ONE)).isTrue();
     }
 
     @Test
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index abd2b76..fcf9200 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -112,6 +112,7 @@
     <uses-permission android:name="android.permission.REGISTER_WINDOW_MANAGER_LISTENERS" />
     <uses-permission android:name="android.permission.SET_ORIENTATION" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.MONITOR_INPUT" />
 
     <!-- DreamManager -->
     <uses-permission android:name="android.permission.READ_DREAM_STATE" />
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
index 105be46..58d50ea 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
@@ -28,7 +28,7 @@
 public interface ClockPlugin extends Plugin {
 
     String ACTION = "com.android.systemui.action.PLUGIN_CLOCK";
-    int VERSION = 3;
+    int VERSION = 4;
 
     /**
      * Get the name of the clock face.
@@ -72,6 +72,14 @@
     }
 
     /**
+     * Allows the plugin to clean up resources when no longer needed.
+     *
+     * Called when the view previously created by {@link ClockPlugin#getView()} has been detached
+     * from the view hierarchy.
+     */
+    void onDestroyView();
+
+    /**
      * Set clock paint style.
      * @param style The new style to set in the paint.
      */
diff --git a/packages/SystemUI/res/drawable-nodpi/icon.xml b/packages/SystemUI/res/drawable-nodpi/icon.xml
index 48094c4..7a68c03 100644
--- a/packages/SystemUI/res/drawable-nodpi/icon.xml
+++ b/packages/SystemUI/res/drawable-nodpi/icon.xml
@@ -15,5 +15,5 @@
 -->
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
     <background android:drawable="@drawable/icon_bg"/>
-    <foreground android:drawable="@drawable/p"/>
+    <foreground android:drawable="@drawable/q"/>
 </adaptive-icon>
diff --git a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml
index 31ecf7e..2a54dfa 100644
--- a/packages/SystemUI/res/drawable-nodpi/icon_bg.xml
+++ b/packages/SystemUI/res/drawable-nodpi/icon_bg.xml
@@ -14,5 +14,5 @@
     limitations under the License.
 -->
 <color xmlns:android="http://schemas.android.com/apk/res/android"
-    android:color="#C5E1A5" />
+    android:color="#77C360" />
 
diff --git a/packages/SystemUI/res/drawable-nodpi/p.xml b/packages/SystemUI/res/drawable-nodpi/p.xml
deleted file mode 100644
index 596b782..0000000
--- a/packages/SystemUI/res/drawable-nodpi/p.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<!--
-Copyright (C) 2018 The Android Open Source Project
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="108dp"
-    android:height="108dp"
-    android:viewportWidth="108"
-    android:viewportHeight="108">
-  <path
-      android:pathData="M49,65L54,65C60.075,65 65,60.075 65,54C65,47.925 60.075,43 54,43C47.925,43 43,47.925 43,54L43,108"
-      android:strokeWidth="16"
-      android:fillColor="#00000000"
-      android:strokeColor="#7CB342"
-      android:fillType="evenOdd"/>
-  <path
-      android:pathData="M51,65L54,65C60.075,65 65,60.075 65,54C65,47.925 60.075,43 54,43C47.925,43 43,47.925 43,54L43,108"
-      android:strokeWidth="8"
-      android:fillColor="#00000000"
-      android:strokeColor="#FFFFFF"
-      android:fillType="evenOdd"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable-nodpi/q.xml b/packages/SystemUI/res/drawable-nodpi/q.xml
new file mode 100644
index 0000000..0f42d2e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/q.xml
@@ -0,0 +1,40 @@
+<!--
+Copyright (C) 2019 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="108dp"
+        android:height="108dp"
+        android:viewportWidth="108.0"
+        android:viewportHeight="108.0">
+    <group
+        android:name="scale"
+        android:pivotX="54" android:pivotY="54"
+        android:scaleX="0.9"
+        android:scaleY="0.9">
+        <group
+            android:name="nudge"
+            android:translateX="24"
+            android:translateY="23.5">
+            <path
+                android:name="tail"
+                android:fillColor="#FFFFFF"
+                android:pathData="M21.749674,34.122784l-9.431964,9.529709l-6.31771,-6.2529106l15.736504,-15.899582l64.765724,65.16436l-6.3046494,6.266083z"/>
+            <path
+                android:name="counter"
+                android:fillColor="#FFFFFF"
+                android:pathData="M30,9.32352941 C41.6954418,9.32352941 51.1764706,18.8045582 51.1764706,30.5 C51.1764706,42.1954418 41.6954418,51.6764706 30,51.6764706 C18.3045582,51.6764706 8.82352941,42.1954418 8.82352941,30.5 C8.82352941,18.8045582 18.3045582,9.32352941 30,9.32352941 L30,9.32352941 Z M30,0.5 C13.4314575,0.5 -5.53805368e-15,13.9314575 -7.10542736e-15,30.5 C-1.02401747e-14,47.0685425 13.4314575,60.5 30,60.5 C46.5685425,60.5 60,47.0685425 60,30.5 C59.9805514,13.9395201 46.5604799,0.519448617 30,0.5 Z"/>
+        </group>
+    </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/bubble_flyout.xml b/packages/SystemUI/res/drawable/bubble_flyout.xml
new file mode 100644
index 0000000..5406aaa
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bubble_flyout.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ 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
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <!-- TODO: Add the triangle pointing to the bubble stack. -->
+    <item>
+        <shape android:shape="rectangle">
+            <solid android:color="?android:attr/colorBackgroundFloating" />
+            <corners
+                android:bottomLeftRadius="?android:attr/dialogCornerRadius"
+                android:topLeftRadius="?android:attr/dialogCornerRadius"
+                android:bottomRightRadius="?android:attr/dialogCornerRadius"
+                android:topRightRadius="?android:attr/dialogCornerRadius"
+            />
+        </shape>
+    </item>
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_notification_gentle.xml b/packages/SystemUI/res/drawable/ic_notification_gentle.xml
new file mode 100644
index 0000000..7074130
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_notification_gentle.xml
@@ -0,0 +1,40 @@
+<!--
+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.
+-->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/back">
+        <shape android:shape="oval">
+            <solid
+                android:color="@color/GM2_green_500" />
+            <size
+                android:height="36dp"
+                android:width="36dp"/>
+        </shape>
+    </item>
+    <item
+        android:id="@+id/fore"
+        android:gravity="center">
+        <vector
+                android:width="32dp"
+                android:height="32dp"
+                android:viewportWidth="24"
+                android:viewportHeight="24">
+            <path
+                android:fillColor="#FFFFFFFF"
+                android:pathData="M15,14.5c-1.38,0 -2.5,-1.12 -2.5,-2.5c0,-0.28 -0.22,-0.5 -0.5,-0.5s-0.5,0.22 -0.5,0.5c0,1.38 -1.12,2.5 -2.5,2.5S6.5,13.38 6.5,12c0,-0.28 -0.22,-0.5 -0.5,-0.5c-0.24,0 -0.46,0.18 -0.49,0.42C5.41,12.55 4.89,13 4.27,13H2v-2h1.71C4.1,10.11 5,9.5 6,9.5c1.38,0 2.5,1.12 2.5,2.5c0,0.28 0.22,0.5 0.5,0.5s0.5,-0.22 0.5,-0.5c0,-1.38 1.12,-2.5 2.5,-2.5s2.5,1.12 2.5,2.5c0,0.28 0.22,0.5 0.5,0.5s0.5,-0.22 0.5,-0.5c0,-1.38 1.12,-2.5 2.5,-2.5c1.02,0 1.91,0.6 2.29,1.5H22v2h-2.27c-0.62,0 -1.14,-0.45 -1.23,-1.08c-0.04,-0.24 -0.25,-0.42 -0.49,-0.42c-0.28,0 -0.5,0.22 -0.5,0.5C17.5,13.38 16.38,14.5 15,14.5z"/>
+        </vector>
+    </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/ic_notification_interruptive.xml b/packages/SystemUI/res/drawable/ic_notification_interruptive.xml
new file mode 100644
index 0000000..0a8b3b8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_notification_interruptive.xml
@@ -0,0 +1,41 @@
+<!--
+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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/back">
+        <shape android:shape="oval">
+            <solid
+                android:color="@color/GM2_yellow_500" />
+            <size
+                android:height="36dp"
+                android:width="36dp"/>
+        </shape>
+    </item>
+    <item
+        android:id="@+id/fore"
+        android:gravity="center">
+        <vector
+                android:width="32dp"
+                android:height="32dp"
+                android:viewportWidth="24"
+                android:viewportHeight="24">
+            <path
+                android:fillColor="#FFFFFFFF"
+                android:pathData="M8.98,16.65c-0.47,0 -0.91,-0.27 -1.12,-0.69l-1.93,-4.61L5.46,12.3c-0.21,0.43 -0.64,0.69 -1.12,0.69H2v-2h1.88l1,-2C5.1,8.56 5.52,8.3 6,8.3s0.9,0.26 1.12,0.69l1.73,4.14l2,-7c0.2,-0.46 0.65,-0.76 1.15,-0.76s0.95,0.3 1.15,0.76l0.04,0.12l1.96,6.88l1.7,-4.08c0.49,-0.98 1.84,-0.91 2.26,-0.06l1,2H22v2h-2.35c-0.47,0 -0.91,-0.27 -1.12,-0.7l-0.47,-0.95l-1.9,4.55c-0.25,0.5 -0.69,0.77 -1.18,0.75c-0.48,-0.01 -0.92,-0.31 -1.11,-0.76l-0.04,-0.12L12,9.37l-1.87,6.52c-0.19,0.45 -0.63,0.74 -1.11,0.76C9.01,16.65 9,16.65 8.98,16.65zM20.32,11.4L20.32,11.4C20.32,11.4 20.32,11.4 20.32,11.4z" />
+        </vector>
+    </item>
+</layer-list>
diff --git a/packages/SystemUI/res/layout/bubble_flyout.xml b/packages/SystemUI/res/layout/bubble_flyout.xml
new file mode 100644
index 0000000..74c6c12
--- /dev/null
+++ b/packages/SystemUI/res/layout/bubble_flyout.xml
@@ -0,0 +1,33 @@
+<!--
+  ~ 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
+  -->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/bubble_flyout"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:background="@drawable/bubble_flyout"
+    android:padding="@dimen/bubble_flyout_padding"
+    android:translationZ="@dimen/bubble_flyout_elevation">
+
+    <TextView
+        android:id="@+id/bubble_flyout_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:maxLines="2"
+        android:maxWidth="@dimen/bubble_flyout_maxwidth"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml
index 13186fc..a8eb2914 100644
--- a/packages/SystemUI/res/layout/bubble_view.xml
+++ b/packages/SystemUI/res/layout/bubble_view.xml
@@ -27,12 +27,4 @@
         android:padding="@dimen/bubble_view_padding"
         android:clipToPadding="false"/>
 
-    <TextView
-        android:id="@+id/message_view"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:minWidth="@dimen/bubble_message_min_width"
-        android:maxWidth="@dimen/bubble_message_max_width"
-        android:padding="@dimen/bubble_message_padding"/>
-
 </com.android.systemui.bubbles.BubbleView>
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 0d44931..f7c6c43 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -24,6 +24,7 @@
     android:clipChildren="false"
     android:clipToPadding="false"
     android:orientation="vertical"
+    android:paddingStart="@*android:dimen/notification_content_margin_start"
     android:background="@color/notification_guts_bg_color">
 
     <!-- Package Info -->
@@ -31,7 +32,6 @@
         android:id="@+id/header"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginStart="@*android:dimen/notification_content_margin_start"
         android:clipChildren="false"
         android:clipToPadding="false">
         <ImageView
@@ -44,7 +44,7 @@
             android:id="@+id/pkgname"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Info"
+            style="@style/TextAppearance.NotificationInfo.Primary"
             android:layout_marginStart="3dp"
             android:layout_marginEnd="2dp"
             android:singleLine="true"
@@ -54,7 +54,7 @@
             android:id="@+id/pkg_divider"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Info"
+            style="@style/TextAppearance.NotificationInfo.Primary"
             android:layout_marginStart="2dp"
             android:layout_marginEnd="2dp"
             android:text="@*android:string/notification_header_divider_symbol"
@@ -64,7 +64,7 @@
             android:id="@+id/delegate_name"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Info"
+            style="@style/TextAppearance.NotificationInfo.Primary"
             android:layout_marginStart="2dp"
             android:layout_marginEnd="2dp"
             android:ellipsize="end"
@@ -77,286 +77,284 @@
             android:layout_height="wrap_content"
             android:layout_centerVertical="true"
             android:layout_alignParentEnd="true"
-            android:paddingHorizontal="16dp"
+
             android:orientation="horizontal">
             <!-- Optional link to app. Only appears if the channel is not disabled and the app
 asked for it -->
             <ImageButton
                 android:id="@+id/app_settings"
-                android:layout_width="40dp"
-                android:layout_height="56dp"
+                android:layout_width="48dp"
+                android:layout_height="48dp"
                 android:layout_centerVertical="true"
-                android:paddingRight="16dp"
                 android:visibility="gone"
                 android:background="@drawable/ripple_drawable"
                 android:contentDescription="@string/notification_app_settings"
-                android:src="@drawable/ic_settings"
-                android:tint="?android:attr/colorAccent" />
+                android:src="@drawable/ic_info"
+                android:tint="@color/notification_guts_link_icon_tint" />
             <!-- 24 dp icon with 16 dp padding all around to mirror notification content margins -->
             <ImageButton
                 android:id="@+id/info"
-                android:layout_width="24dp"
-                android:layout_height="56dp"
+                android:layout_width="48dp"
+                android:layout_height="48dp"
                 android:layout_centerVertical="true"
                 android:background="@drawable/ripple_drawable"
                 android:contentDescription="@string/notification_more_settings"
-                android:src="@drawable/ic_info"
-                android:tint="?android:attr/colorAccent" />
+                android:src="@drawable/ic_settings"
+                android:tint="@color/notification_guts_link_icon_tint" />
         </LinearLayout>
     </RelativeLayout>
 
+    <!-- Channel Info Block -->
     <LinearLayout
-        android:id="@+id/prompt"
+        android:id="@+id/channel_info"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:layout_marginBottom="@dimen/notification_guts_button_spacing"
+        android:paddingEnd="@*android:dimen/notification_content_margin_end"
+        android:orientation="vertical">
+        <RelativeLayout
+            android:id="@+id/names"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+            <TextView
+                android:id="@+id/group_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                style="@style/TextAppearance.NotificationInfo.Primary"
+                android:layout_marginStart="2dp"
+                android:layout_marginEnd="2dp"
+                android:ellipsize="end"
+                android:maxLines="1"
+                android:layout_centerVertical="true" />
+            <TextView
+                android:id="@+id/pkg_group_divider"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                style="@style/TextAppearance.NotificationInfo.Primary"
+                android:layout_marginStart="2dp"
+                android:layout_marginEnd="2dp"
+                android:text="@*android:string/notification_header_divider_symbol"
+                android:layout_centerVertical="true"
+                android:layout_toEndOf="@id/group_name" />
+            <!-- Channel Name -->
+            <TextView
+                android:id="@+id/channel_name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                style="@style/TextAppearance.NotificationInfo.Primary"
+                android:layout_toEndOf="@id/pkg_group_divider"/>
+        </RelativeLayout>
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/blocking_helper"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/notification_guts_button_spacing"
+        android:paddingEnd="@*android:dimen/notification_content_margin_end"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:orientation="vertical">
+        <!-- blocking helper text. no need for non-configurable check b/c controls won't be
+        activated in that case -->
+        <TextView
+            android:id="@+id/blocking_helper_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="2dp"
+            android:text="@string/inline_blocking_helper"
+            style="@*android:style/TextAppearance.DeviceDefault.Notification" />
+        <RelativeLayout
+            android:id="@+id/block_buttons"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/notification_guts_button_spacing">
+            <TextView
+                android:id="@+id/blocking_helper_turn_off_notifications"
+                android:text="@string/inline_turn_off_notifications"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:layout_alignParentStart="true"
+                android:width="110dp"
+                android:paddingEnd="15dp"
+                android:breakStrategy="simple"
+                style="@style/TextAppearance.NotificationInfo.Button"/>
+            <TextView
+                android:id="@+id/deliver_silently"
+                android:text="@string/inline_deliver_silently_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:layout_marginStart="@dimen/notification_guts_button_horizontal_spacing"
+                android:paddingEnd="15dp"
+                android:width="110dp"
+                android:breakStrategy="simple"
+                android:layout_toStartOf="@+id/keep_showing"
+                style="@style/TextAppearance.NotificationInfo.Button"/>
+            <TextView
+                android:id="@+id/keep_showing"
+                android:text="@string/inline_keep_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:layout_marginStart="@dimen/notification_guts_button_horizontal_spacing"
+                android:width="110dp"
+                android:breakStrategy="simple"
+                android:layout_alignParentEnd="true"
+                style="@style/TextAppearance.NotificationInfo.Button"/>
+        </RelativeLayout>
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/inline_controls"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/notification_guts_button_spacing"
+        android:paddingEnd="@*android:dimen/notification_content_margin_end"
         android:clipChildren="false"
         android:clipToPadding="false"
         android:orientation="vertical">
 
-        <!-- Channel Info Block -->
-        <LinearLayout
+        <!-- Non configurable app/channel text. appears instead of @+id/interruptiveness_settings-->
+        <TextView
+            android:id="@+id/non_configurable_text"
+            android:text="@string/notification_unblockable_desc"
+            android:visibility="gone"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginStart="@*android:dimen/notification_content_margin_start"
-            android:layout_marginEnd="@*android:dimen/notification_content_margin_start"
+            android:paddingTop="@dimen/notification_guts_option_vertical_padding"
+            style="@*android:style/TextAppearance.DeviceDefault.Notification" />
+
+        <!-- Non configurable multichannel text. appears instead of @+id/interruptiveness_settings-->
+        <TextView
+            android:id="@+id/non_configurable_multichannel_text"
+            android:text="@string/notification_multichannel_desc"
+            android:visibility="gone"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="@dimen/notification_guts_option_vertical_padding"
+            style="@*android:style/TextAppearance.DeviceDefault.Notification" />
+
+        <LinearLayout
+            android:id="@+id/interruptiveness_settings"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
             android:orientation="vertical">
-            <RelativeLayout
-                android:id="@+id/names"
+            <!-- Interruptive row -->
+            <LinearLayout
+                android:id="@+id/alert_row"
                 android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-                <TextView
-                    android:id="@+id/group_name"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
-                    android:layout_marginStart="2dp"
-                    android:layout_marginEnd="2dp"
-                    android:ellipsize="end"
-                    android:maxLines="1"
-                    android:layout_centerVertical="true" />
-                <TextView
-                    android:id="@+id/pkg_group_divider"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
-                    android:layout_marginStart="2dp"
-                    android:layout_marginEnd="2dp"
-                    android:text="@*android:string/notification_header_divider_symbol"
-                    android:layout_centerVertical="true"
-                    android:layout_toEndOf="@id/group_name" />
-                <!-- Channel Name -->
-                <TextView
-                    android:id="@+id/channel_name"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    style="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
-                    android:layout_toEndOf="@id/pkg_group_divider"/>
-            </RelativeLayout>
-            <!-- Question prompt -->
-            <TextView
-                android:id="@+id/block_prompt"
-                android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                style="@*android:style/TextAppearance.DeviceDefault.Notification" />
+                android:paddingTop="@dimen/notification_guts_option_vertical_padding"
+                android:paddingBottom="@dimen/notification_guts_option_vertical_padding"
+                android:paddingStart="@dimen/notification_guts_option_horizontal_padding"
+                android:orientation="horizontal">
+
+                <ImageView
+                    android:id="@+id/int_alert"
+                    android:src="@drawable/ic_notification_interruptive"
+                    android:background="@android:color/transparent"
+                    android:layout_gravity="center"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:contentDescription="@string/inline_silent_button_alert"/>
+
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingStart="@dimen/notification_guts_option_horizontal_padding"
+                    android:paddingEnd="@dimen/notification_guts_option_horizontal_padding"
+                    android:orientation="vertical">
+                    <TextView
+                        android:id="@+id/int_alert_label"
+                        android:text="@string/inline_silent_button_alert"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:ellipsize="end"
+                        android:maxLines="1"
+                        style="@style/TextAppearance.NotificationInfo.Primary"/>
+                    <TextView
+                        android:id="@+id/int_alert_summary"
+                        android:text="@string/hint_text_alert"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:ellipsize="end"
+                        style="@style/TextAppearance.NotificationInfo.Secondary"/>
+                </LinearLayout>
+            </LinearLayout>
+
+            <!-- Gentle row -->
+            <LinearLayout
+                android:id="@+id/silent_row"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingTop="@dimen/notification_guts_option_vertical_padding"
+                android:paddingBottom="@dimen/notification_guts_option_vertical_padding"
+                android:paddingStart="@dimen/notification_guts_option_horizontal_padding"
+                android:layout_marginTop="@dimen/notification_guts_option_vertical_margin"
+                android:orientation="horizontal">
+                <ImageView
+                    android:id="@+id/int_silent"
+                    android:src="@drawable/ic_notification_gentle"
+                    android:layout_gravity="center"
+                    android:layout_width="36dp"
+                    android:layout_height="36dp"
+                    android:background="@android:color/transparent"
+                    android:contentDescription="@string/inline_silent_button_silent"/>
+                <LinearLayout
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:orientation="vertical"
+                    android:paddingStart="@dimen/notification_guts_option_horizontal_padding"
+                    android:paddingEnd="@dimen/notification_guts_option_horizontal_padding">
+                    <TextView
+                        android:id="@+id/int_silent_label"
+                        android:text="@string/inline_silent_button_silent"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:ellipsize="end"
+                        android:maxLines="1"
+                        style="@style/TextAppearance.NotificationInfo.Primary"/>
+                    <TextView
+                        android:id="@+id/int_silent_summary"
+                        android:text="@string/hint_text_silent"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:ellipsize="end"
+                        style="@style/TextAppearance.NotificationInfo.Secondary"/>
+                </LinearLayout>
+            </LinearLayout>
         </LinearLayout>
 
-        <!-- Settings and Done buttons -->
         <RelativeLayout
-            android:id="@+id/block_or_minimize"
+            android:id="@+id/bottom_buttons"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginTop="@dimen/notification_guts_button_spacing"
-            android:layout_marginStart="@dimen/notification_guts_button_side_margin"
-            android:layout_marginEnd="@dimen/notification_guts_button_side_margin"
-            android:clipChildren="false"
-            android:clipToPadding="false">
+            android:paddingTop="@dimen/notification_guts_button_spacing"
+            android:paddingBottom="@dimen/notification_guts_button_spacing">
+            <TextView
+                android:id="@+id/turn_off_notifications"
+                android:text="@string/inline_turn_off_notifications"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:layout_alignParentStart="true"
+                android:maxWidth="200dp"
+                style="@style/TextAppearance.NotificationInfo.Button"/>
             <TextView
                 android:id="@+id/done"
                 android:text="@string/inline_ok_button"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_centerVertical="true"
-                android:maxWidth="100dp"
-                style="@style/TextAppearance.NotificationInfo.Button"/>
-
-            <LinearLayout
-                android:id="@+id/block_buttons"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_centerVertical="true"
+                android:maxWidth="125dp"
                 android:layout_alignParentEnd="true"
-                android:maxWidth="200dp"
-                android:orientation="horizontal">
-                <TextView
-                    android:id="@+id/deliver_silently"
-                    android:text="@string/inline_silent_button_silent"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_centerVertical="true"
-                    android:layout_marginStart="@dimen/notification_guts_button_horizontal_spacing"
-                    android:paddingRight="24dp"
-                    android:maxWidth="125dp"
-                    style="@style/TextAppearance.NotificationInfo.Button"/>
-                <TextView
-                    android:id="@+id/block"
-                    android:text="@string/inline_block_button"
-                    android:minWidth="48dp"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_centerVertical="true"
-                    android:maxWidth="75dp"
-                    android:layout_marginStart="@dimen/notification_guts_button_horizontal_spacing"
-                    style="@style/TextAppearance.NotificationInfo.Button"/>
-                <TextView
-                    android:id="@+id/minimize"
-                    android:text="@string/inline_minimize_button"
-                    android:minWidth="48dp"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_centerVertical="true"
-                    android:maxWidth="75dp"
-                    android:layout_marginStart="@dimen/notification_guts_button_horizontal_spacing"
-                    style="@style/TextAppearance.NotificationInfo.Button"/>
-            </LinearLayout>
+                style="@style/TextAppearance.NotificationInfo.Button"/>
         </RelativeLayout>
-        <LinearLayout
-            android:id="@+id/interruptiveness_settings"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical"
-            android:visibility="gone">
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="2dp"
-                android:layout_marginStart="@dimen/notification_guts_button_side_margin"
-                android:layout_marginEnd="@dimen/notification_guts_button_side_margin"
-                android:gravity="center"
-                android:orientation="horizontal">
-                <LinearLayout
-                    android:layout_width="0dp"
-                    android:layout_height="match_parent"
-                    android:layout_weight="1"
-                    android:gravity="center_horizontal"
-                    android:orientation="vertical">
-                    <FrameLayout
-                        android:id="@+id/int_block_wrapper"
-                        android:padding="4dp"
-                        android:layout_width="48dp"
-                        android:layout_height="48dp"
-                        android:gravity="center">
-                        <ImageButton
-                            android:id="@+id/int_block"
-                            android:background="@drawable/circle_white_40dp"
-                            android:src="@drawable/ic_notification_block"
-                            android:layout_gravity="center"
-                            android:layout_width="40dp"
-                            android:layout_height="40dp"
-                            android:clickable="false"
-                            android:contentDescription="@string/inline_block_button"
-                            android:tint="@color/GM2_grey_400"
-                            style="@style/TextAppearance.NotificationInfo.Button"/>
-                    </FrameLayout>
-                    <TextView
-                        android:id="@+id/int_block_label"
-                        android:text="@string/inline_block_button"
-                        android:layout_gravity="center_horizontal"
-                        android:gravity="center"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:ellipsize="end"
-                        android:maxLines="1"
-                        style="@style/TextAppearance.NotificationInfo.ButtonLabel"/>
-                </LinearLayout>
-                <LinearLayout
-                    android:layout_width="0dp"
-                    android:layout_height="match_parent"
-                    android:layout_weight="1"
-                    android:gravity="center_horizontal"
-                    android:orientation="vertical">
-                    <FrameLayout
-                        android:id="@+id/int_silent_wrapper"
-                        android:padding="4dp"
-                        android:layout_width="48dp"
-                        android:layout_height="48dp"
-                        android:gravity="center">
-                        <ImageButton
-                            android:id="@+id/int_silent"
-                            android:background="@drawable/circle_white_40dp"
-                            android:src="@drawable/ic_notifications_silence"
-                            android:layout_gravity="center"
-                            android:layout_width="40dp"
-                            android:layout_height="40dp"
-                            android:clickable="false"
-                            android:contentDescription="@string/inline_silent_button_silent"
-                            android:tint="@color/GM2_grey_400"
-                            style="@style/TextAppearance.NotificationInfo.Button"/>
-                    </FrameLayout>
-                    <TextView
-                        android:id="@+id/int_silent_label"
-                        android:text="@string/inline_silent_button_silent"
-                        android:layout_gravity="center_horizontal"
-                        android:gravity="center"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:ellipsize="end"
-                        android:maxLines="1"
-                        style="@style/TextAppearance.NotificationInfo.ButtonLabel"/>
-                </LinearLayout>
-                <LinearLayout
-                    android:layout_width="0dp"
-                    android:layout_height="match_parent"
-                    android:layout_weight="1"
-                    android:gravity="center_horizontal"
-                    android:orientation="vertical">
-                    <FrameLayout
-                        android:id="@+id/int_alert_wrapper"
-                        android:padding="4dp"
-                        android:layout_width="48dp"
-                        android:layout_height="48dp"
-                        android:gravity="center">
-                        <ImageButton
-                            android:id="@+id/int_alert"
-                            android:background="@drawable/circle_white_40dp"
-                            android:src="@drawable/ic_notifications_alert"
-                            android:layout_gravity="center"
-                            android:layout_width="40dp"
-                            android:layout_height="40dp"
-                            android:contentDescription="@string/inline_silent_button_alert"
-                            android:clickable="false"
-                            android:tint="@color/GM2_grey_400"
-                            style="@style/TextAppearance.NotificationInfo.Button"/>
-                    </FrameLayout>
-                    <TextView
-                        android:id="@+id/int_alert_label"
-                        android:text="@string/inline_silent_button_alert"
-                        android:layout_gravity="center_horizontal"
-                        android:gravity="center"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:ellipsize="end"
-                        android:maxLines="1"
-                        style="@style/TextAppearance.NotificationInfo.ButtonLabel"/>
-                </LinearLayout>
-            </LinearLayout>
-            <TextView
-                android:id="@+id/hint_text"
-                android:layout_marginStart="@*android:dimen/notification_content_margin_start"
-                android:layout_marginEnd="@*android:dimen/notification_content_margin_start"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                style="@style/TextAppearance.NotificationInfo.HintText" />
-            <TextView
-                android:id="@+id/done_button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="right"
-                android:paddingRight="24dp"
-                android:text="@string/inline_done_button"
-                style="@style/TextAppearance.NotificationInfo.Button" />
-        </LinearLayout>
+
     </LinearLayout>
 
     <com.android.systemui.statusbar.notification.row.NotificationUndoLayout
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index d74d258..8a0aaea 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -42,6 +42,10 @@
     <!-- The color of the text inside a notification -->
     <color name="notification_primary_text_color">@*android:color/notification_primary_text_color_dark</color>
 
+    <color name="notification_guts_selection_bg">#202124</color>
+    <color name="notification_guts_selection_border">#669DF6</color>
+    <color name="notification_guts_link_icon_tint">@color/GM2_grey_200</color>
+
     <!-- The color of the background in the top part of QSCustomizer -->
     <color name="qs_customize_background">@color/GM2_grey_900</color>
 
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 290d75b..b2a5075 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -88,6 +88,10 @@
     <!-- The "inside" of a notification, reached via longpress -->
     <color name="notification_guts_bg_color">@color/GM2_grey_50</color>
 
+    <color name="notification_guts_selection_bg">#FFFFFF</color>
+    <color name="notification_guts_selection_border">#4285F4</color>
+    <color name="notification_guts_link_icon_tint">@color/GM2_grey_900</color>
+
     <color name="assist_orb_color">#ffffff</color>
 
     <color name="keyguard_user_switcher_background_gradient_color">#77000000</color>
@@ -163,4 +167,7 @@
 
     <color name="GM2_red_300">#F28B82</color>
     <color name="GM2_red_500">#B71C1C</color>
+
+    <color name="GM2_yellow_500">#FFFBBC04</color>
+    <color name="GM2_green_500">#FF34A853</color>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index f8295eb..b0afe75 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -109,12 +109,12 @@
 
     <!-- The default tiles to display in QuickSettings -->
     <string name="quick_settings_tiles_default" translatable="false">
-        wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,sensorprivacy
+        wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast
     </string>
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
-        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night,sensorprivacy
+        wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,work,cast,night
     </string>
 
     <!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6eb279a..a02469e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -38,6 +38,14 @@
     <dimen name="navigation_handle_sample_horizontal_margin">10dp</dimen>
     <dimen name="navigation_home_handle_width">72dp</dimen>
 
+
+    <!-- Size of the nav bar edge panels, should be greater to the
+         edge sensitivity + the drag threshold -->
+    <dimen name="navigation_edge_panel_width">52dp</dimen>
+    <dimen name="navigation_edge_panel_height">52dp</dimen>
+    <!-- The threshold to drag to trigger the edge action -->
+    <dimen name="navigation_edge_action_drag_threshold">16dp</dimen>
+
     <!-- Luminance threshold to determine black/white contrast for the navigation affordances -->
     <item name="navigation_luminance_threshold" type="dimen" format="float">0.5</item>
     <!-- Luminance change threshold that allows applying new value if difference was exceeded -->
@@ -171,7 +179,7 @@
     <dimen name="notification_menu_icon_padding">20dp</dimen>
 
     <!-- The vertical space around the buttons in the inline settings -->
-    <dimen name="notification_guts_button_spacing">6dp</dimen>
+    <dimen name="notification_guts_button_spacing">12dp</dimen>
 
     <!-- Extra horizontal space for properly aligning guts buttons with the notification content -->
     <dimen name="notification_guts_button_side_margin">8dp</dimen>
@@ -188,6 +196,18 @@
     <!-- The height of the header in inline settings -->
     <dimen name="notification_guts_header_height">24dp</dimen>
 
+    <!-- The text size of the header in inline settings -->
+    <dimen name="notification_guts_header_text_size">16sp</dimen>
+
+    <!-- The horizontal space between items in the alert selections in the inline settings -->
+    <dimen name="notification_guts_option_horizontal_padding">15dp</dimen>
+
+    <!-- The vertical space between items in the alert selections in the inline settings -->
+    <dimen name="notification_guts_option_vertical_padding">15dp</dimen>
+
+    <!-- The vertical space between the alert selections in the inline settings -->
+    <dimen name="notification_guts_option_vertical_margin">6dp</dimen>
+
     <!-- The minimum height for the snackbar shown after the snooze option has been chosen. -->
     <dimen name="snooze_snackbar_min_height">56dp</dimen>
 
@@ -1027,6 +1047,12 @@
 
     <!-- How much each bubble is elevated. -->
     <dimen name="bubble_elevation">1dp</dimen>
+    <!-- How much the bubble flyout text container is elevated. -->
+    <dimen name="bubble_flyout_elevation">4dp</dimen>
+    <!-- How much padding is around the flyout text. -->
+    <dimen name="bubble_flyout_padding">16dp</dimen>
+    <!-- The maximum width of a bubble flyout. -->
+    <dimen name="bubble_flyout_maxwidth">200dp</dimen>
     <!-- Padding around a collapsed bubble -->
     <dimen name="bubble_view_padding">0dp</dimen>
     <!-- Padding between bubbles when displayed in expanded state -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 0411d015..f47d4b5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -670,6 +670,9 @@
         <item quantity="other"><xliff:g id="number" example="3">%s</xliff:g> more notifications inside.</item>
     </plurals>
 
+    <!-- Format to use to summarize a message from a contact in a single line of text. For example: "Julia: How's it going?". [CHAR LIMIT=NONE] -->
+    <string name="notification_summary_message_format"><xliff:g id="contact_name" example="Julia">%1$s</xliff:g>: <xliff:g id="message_content" example="How is it going?">%2$s</xliff:g></string>
+
     <!-- Content description of button in notification inspector for system settings relating to
          notifications from this application [CHAR LIMIT=NONE] -->
     <string name="status_bar_notification_inspect_item_title">Notification settings</string>
@@ -1599,7 +1602,7 @@
     <string name="inline_done_button">Done</string>
 
     <!-- Notification Inline controls: button to dismiss the blocking helper [CHAR_LIMIT=20] -->
-    <string name="inline_ok_button">OK</string>
+    <string name="inline_ok_button">Apply</string>
 
     <!-- Notification Inline controls: continue receiving notifications prompt, channel level -->
     <string name="inline_keep_showing">Keep showing these notifications?</string>
@@ -1620,17 +1623,20 @@
     <string name="inline_minimize_button">Minimize</string>
 
     <!-- Notification inline controls: button to show notifications silently, without alerting the user [CHAR_LIMIT=35] -->
-    <string name="inline_silent_button_silent">Show silently</string>
+    <string name="inline_silent_button_silent">Gentle</string>
 
     <!-- Notification inline controls: button to continue showing notifications silently [CHAR_LIMIT=35] -->
     <string name="inline_silent_button_stay_silent">Stay silent</string>
 
     <!-- Notification inline controls: button to make notifications alert the user [CHAR_LIMIT=35] -->
-    <string name="inline_silent_button_alert">Alert</string>
+    <string name="inline_silent_button_alert">Interruptive</string>
 
     <!-- Notification inline controls: button to continue alerting the user when notifications arrive [CHAR_LIMIT=35] -->
     <string name="inline_silent_button_keep_alerting">Keep alerting</string>
 
+    <!-- Notification inline controls: button to show block screen [CHAR_LIMIT=35] -->
+    <string name="inline_turn_off_notifications">Turn off notifications</string>
+
     <!-- Notification Inline controls: continue receiving notifications prompt, app level -->
     <string name="inline_keep_showing_app">Keep showing notifications from this app?</string>
 
@@ -1641,11 +1647,14 @@
     <string name="hint_text_silent">Silent notifications appear in the shade, but do not appear on the lock screen, present a banner, or play a sound.</string>
 
     <!-- Hint text for alert button in the interruptiveness settings [CHAR_LIMIT=NONE]-->
-    <string name="hint_text_alert">Alerted notifications appear in the shade, on the lock screen, present a banner, and play a sound.</string>
+    <string name="hint_text_alert">These notifications will make a sound and show in the notification drawer, status bar, and lock screen</string>
 
     <!-- Notification: Control panel: Label that displays when the app's notifications cannot be blocked. -->
     <string name="notification_unblockable_desc">These notifications can\'t be turned off</string>
 
+    <!-- Notification: Control panel: label that displays when viewing settings for a group of notifications posted to multiple channels. -->
+    <string name="notification_multichannel_desc">This group of notifications cannot be configured here</string>
+
     <!-- Notification: Control panel: Label for the app that posted this notification, if it's not the package that the notification was posted for -->
     <string name="notification_delegate_header">via <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
 
@@ -2401,5 +2410,4 @@
     <string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string>
     <!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]-->
     <string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string>
-
 </resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7c123ef..2ff481f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -435,6 +435,7 @@
     </style>
 
     <style name="TextAppearance.NotificationInfo.Primary">
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
         <item name="android:textSize">16sp</item>
         <item name="android:alpha">0.87</item>
     </style>
@@ -471,16 +472,12 @@
     </style>
 
     <style name="TextAppearance.NotificationInfo.Button">
-        <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item>
-        <item name="android:textSize">14sp</item>
+        <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+        <item name="android:textSize">16sp</item>
         <item name="android:textColor">?android:attr/colorAccent</item>
         <item name="android:background">@drawable/btn_borderless_rect</item>
         <item name="android:gravity">center</item>
         <item name="android:focusable">true</item>
-        <item name="android:paddingTop">@dimen/notification_guts_button_vertical_padding</item>
-        <item name="android:paddingBottom">@dimen/notification_guts_button_vertical_padding</item>
-        <item name="android:paddingLeft">@dimen/notification_guts_button_horizontal_padding</item>
-        <item name="android:paddingRight">@dimen/notification_guts_button_horizontal_padding</item>
     </style>
 
     <style name="TextAppearance.HeadsUpStatusBarText"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index ce615b6..04701bc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -119,4 +119,14 @@
      */
     void onAssistantAvailable(boolean available) = 13;
 
+    /**
+     * Sent when the assistant changes how visible it is to the user.
+     */
+    void onAssistantVisibilityChanged(float visibility) = 14;
+
+    /*
+     * Sent when back is triggered.
+     */
+    void onBackAction(boolean completed, int downX, int downY, boolean isButton,
+            boolean gestureSwipeLeft) = 15;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 953816e..5764fe8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -92,4 +92,9 @@
      * Start the assistant.
      */
     void startAssistant(in Bundle bundle) = 13;
+
+    /**
+     * Creates a new gesture monitor
+     */
+    Bundle monitorGestureInput(String name, int displayId) = 14;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.java
new file mode 100644
index 0000000..ddca723
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputMonitorCompat.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.systemui.shared.system;
+
+import android.os.Bundle;
+import android.os.Looper;
+import android.view.Choreographer;
+import android.view.InputMonitor;
+
+import com.android.systemui.shared.system.InputChannelCompat.InputEventListener;
+import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
+
+/**
+ * @see android.view.InputMonitor
+ */
+public class InputMonitorCompat {
+
+    private final InputMonitor mInputMonitor;
+
+    private InputMonitorCompat(InputMonitor monitor) {
+        mInputMonitor = monitor;
+    }
+
+    /**
+     * @see InputMonitor#pilferPointers()
+     */
+    public void pilferPointers() {
+        mInputMonitor.pilferPointers();
+    }
+
+    /**
+     * @see InputMonitor#dispose()
+     */
+    public void dispose() {
+        mInputMonitor.dispose();
+    }
+
+    /**
+     * @see InputMonitor#getInputChannel()
+     */
+    public InputEventReceiver getInputReceiver(Looper looper, Choreographer choreographer,
+            InputEventListener listener) {
+        return new InputEventReceiver(mInputMonitor.getInputChannel(), looper, choreographer,
+                listener);
+    }
+
+    /**
+     * Gets the input monitor stored in a bundle
+     */
+    public static InputMonitorCompat fromBundle(Bundle bundle, String key) {
+        return new InputMonitorCompat((InputMonitor) bundle.getParcelable(key));
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index d0c17b7..1076e73 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -24,6 +24,8 @@
 import android.content.res.Resources;
 import android.view.WindowManagerPolicyConstants;
 
+import com.android.internal.policy.ScreenDecorationsUtils;
+
 /**
  * Various shared constants between Launcher and SysUI as part of quickstep
  */
@@ -31,6 +33,7 @@
 
     public static final String KEY_EXTRA_SYSUI_PROXY = "extra_sysui_proxy";
     public static final String KEY_EXTRA_INPUT_CHANNEL = "extra_input_channel";
+    public static final String KEY_EXTRA_INPUT_MONITOR = "extra_input_monitor";
     public static final String KEY_EXTRA_WINDOW_CORNER_RADIUS = "extra_window_corner_radius";
     public static final String KEY_EXTRA_SUPPORTS_WINDOW_CORNERS = "extra_supports_window_corners";
 
@@ -129,5 +132,19 @@
                 com.android.internal.R.dimen.config_backGestureInset);
     }
 
+    /**
+     * Corner radius that should be used on windows in order to cover the display.
+     * These values are expressed in pixels because they should not respect display or font
+     * scaling, this means that we don't have to reload them on config changes.
+     */
+    public static float getWindowCornerRadius(Resources resources) {
+        return ScreenDecorationsUtils.getWindowCornerRadius(resources);
+    }
 
+    /**
+     * If live rounded corners are supported on windows.
+     */
+    public static boolean supportsRoundedCornersOnWindows(Resources resources) {
+        return ScreenDecorationsUtils.supportsRoundedCornersOnWindows(resources);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index fd92e9e..b738b57 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -187,6 +187,7 @@
                 mBigClockContainer.removeAllViews();
                 updateBigClockVisibility();
             }
+            mClockPlugin.onDestroyView();
             mClockPlugin = null;
         }
         if (plugin == null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 8ebe1ae..c1bf4d4 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -20,6 +20,8 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
+import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
+
 import android.animation.LayoutTransition;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
@@ -60,6 +62,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
@@ -70,6 +73,9 @@
 import java.util.HashMap;
 import java.util.List;
 
+import javax.inject.Inject;
+import javax.inject.Named;
+
 /**
  * View visible under the clock on the lock screen and AoD.
  */
@@ -80,6 +86,8 @@
     public static final int DEFAULT_ANIM_DURATION = 550;
 
     private final HashMap<View, PendingIntent> mClickActions;
+    private final ActivityStarter mActivityStarter;
+    private final ConfigurationController mConfigurationController;
     private Uri mKeyguardSliceUri;
     @VisibleForTesting
     TextView mTitle;
@@ -99,16 +107,10 @@
     private final int mRowWithHeaderPadding;
     private final int mRowPadding;
 
-    public KeyguardSliceView(Context context) {
-        this(context, null, 0);
-    }
-
-    public KeyguardSliceView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
+    @Inject
+    public KeyguardSliceView(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
+            ActivityStarter activityStarter, ConfigurationController configurationController) {
+        super(context, attrs);
 
         TunerService tunerService = Dependency.get(TunerService.class);
         tunerService.addTunable(this, Settings.Secure.KEYGUARD_SLICE_URI);
@@ -117,6 +119,8 @@
         mRowPadding = context.getResources().getDimensionPixelSize(R.dimen.subtitle_clock_padding);
         mRowWithHeaderPadding = context.getResources()
                 .getDimensionPixelSize(R.dimen.header_subtitle_padding);
+        mActivityStarter = activityStarter;
+        mConfigurationController = configurationController;
 
         LayoutTransition transition = new LayoutTransition();
         transition.setStagger(LayoutTransition.CHANGE_APPEARING, DEFAULT_ANIM_DURATION / 2);
@@ -137,6 +141,7 @@
         mRow = findViewById(R.id.row);
         mTextColor = Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor);
         mIconSize = (int) mContext.getResources().getDimension(R.dimen.widget_icon_size);
+        mTitle.setOnClickListener(this);
     }
 
     @Override
@@ -146,7 +151,7 @@
         mDisplayId = getDisplay().getDisplayId();
         // Make sure we always have the most current slice
         mLiveData.observeForever(this);
-        Dependency.get(ConfigurationController.class).addCallback(this);
+        mConfigurationController.addCallback(this);
     }
 
     @Override
@@ -157,7 +162,7 @@
         if (mDisplayId == DEFAULT_DISPLAY) {
             mLiveData.removeObserver(this);
         }
-        Dependency.get(ConfigurationController.class).removeCallback(this);
+        mConfigurationController.removeCallback(this);
     }
 
     /**
@@ -179,6 +184,7 @@
             Trace.endSection();
             return;
         }
+        mClickActions.clear();
 
         ListContent lc = new ListContent(getContext(), mSlice);
         SliceContent headerContent = lc.getHeader();
@@ -201,9 +207,12 @@
             SliceItem mainTitle = header.getTitleItem();
             CharSequence title = mainTitle != null ? mainTitle.getText() : null;
             mTitle.setText(title);
+            if (header.getPrimaryAction() != null
+                    && header.getPrimaryAction().getAction() != null) {
+                mClickActions.put(mTitle, header.getPrimaryAction().getAction());
+            }
         }
 
-        mClickActions.clear();
         final int subItemsCount = subItems.size();
         final int blendedColor = getTextColor();
         final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it
@@ -289,11 +298,7 @@
     public void onClick(View v) {
         final PendingIntent action = mClickActions.get(v);
         if (action != null) {
-            try {
-                action.send();
-            } catch (PendingIntent.CanceledException e) {
-                Log.i(TAG, "Pending intent cancelled, nothing to launch", e);
-            }
+            mActivityStarter.startPendingIntentDismissingKeyguard(action);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
index 147def39..d30f45f 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
@@ -102,6 +102,16 @@
     }
 
     @Override
+    public void onDestroyView() {
+        mView = null;
+        mDigitalClock = null;
+        mAnalogClock = null;
+        mLockClockContainer = null;
+        mLockClock = null;
+        mDarkController = null;
+    }
+
+    @Override
     public String getName() {
         return "bubble";
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java
index 73414b3..488cb27 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockController.java
@@ -93,6 +93,13 @@
     }
 
     @Override
+    public void onDestroyView() {
+        mView = null;
+        mTextTime = null;
+        mTextDate = null;
+    }
+
+    @Override
     public String getName() {
         return "default";
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java
index ea9f0cd..81b6a60 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/StretchAnalogClockController.java
@@ -101,6 +101,17 @@
         mDarkController = new CrossFadeDarkController(mDigitalClock, mLockClock);
     }
 
+
+    @Override
+    public void onDestroyView() {
+        mBigClockView = null;
+        mDigitalClock = null;
+        mAnalogClock = null;
+        mView = null;
+        mLockClock = null;
+        mDarkController = null;
+    }
+
     @Override
     public String getName() {
         return "stretch";
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java
index 67c0989..1c6b38b 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/TypeClockController.java
@@ -99,6 +99,14 @@
     }
 
     @Override
+    public void onDestroyView() {
+        mView = null;
+        mTypeClock = null;
+        mLockClock = null;
+        mDarkController = null;
+    }
+
+    @Override
     public String getName() {
         return "type";
     }
diff --git a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
index 72ab02c..822538b 100644
--- a/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/HardwareUiLayout.java
@@ -561,4 +561,29 @@
         inoutInfo.contentInsets.set(mList.getLeft(), mList.getTop(),
                 0, getBottom() - mList.getBottom());
     };
+
+    private float getAnimationDistance() {
+        return getContext().getResources().getDimension(
+                com.android.systemui.R.dimen.global_actions_panel_width) / 2;
+    }
+
+    @Override
+    public float getAnimationOffsetX() {
+        if (RotationUtils.getRotation(mContext) == ROTATION_NONE) {
+            return getAnimationDistance();
+        }
+        return 0;
+    }
+
+    @Override
+    public float getAnimationOffsetY() {
+        switch (RotationUtils.getRotation(getContext())) {
+            case RotationUtils.ROTATION_LANDSCAPE:
+                return -getAnimationDistance();
+            case RotationUtils.ROTATION_SEASCAPE:
+                return getAnimationDistance();
+            default: // Portrait
+                return 0;
+        }
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
index d063a0f..a30b681 100644
--- a/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/MultiListLayout.java
@@ -148,6 +148,16 @@
     }
 
     /**
+     * Get the X offset in pixels for use when animating the view onto or off of the screen.
+     */
+    public abstract float getAnimationOffsetX();
+
+    /**
+     * Get the Y offset in pixels for use when animating the view onto or off of the screen.
+     */
+    public abstract float getAnimationOffsetY();
+
+    /**
      * Adapter class for converting items into child views for MultiListLayout and handling
      * callbacks for input events.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 3aa9f73..a5aed87 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -395,7 +395,6 @@
             }
             if (shouldAutoBubbleForFlags(mContext, entry) || shouldBubble(entry)) {
                 // TODO: handle group summaries
-                entry.setIsBubble(true);
                 boolean suppressNotification = entry.getBubbleMetadata() != null
                         && entry.getBubbleMetadata().getSuppressInitialNotification()
                         && isForegroundApp(entry.notification.getPackageName());
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index be55829..de4605b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -42,7 +42,9 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.FrameLayout;
+import android.widget.TextView;
 
 import androidx.annotation.MainThread;
 import androidx.annotation.Nullable;
@@ -70,6 +72,13 @@
     private static final String TAG = "BubbleStackView";
     private static final boolean DEBUG = false;
 
+    /** Duration of the flyout alpha animations. */
+    private static final int FLYOUT_ALPHA_ANIMATION_DURATION = 100;
+
+    /** How long to wait, in milliseconds, before hiding the flyout. */
+    @VisibleForTesting
+    static final int FLYOUT_HIDE_AFTER = 5000;
+
     /**
      * Interface to synchronize {@link View} state and the screen.
      *
@@ -119,6 +128,14 @@
 
     private FrameLayout mExpandedViewContainer;
 
+    private View mFlyout;
+    private TextView mFlyoutText;
+    /** Spring animation for the flyout. */
+    private SpringAnimation mFlyoutSpring;
+    /** Runnable that fades out the flyout and then sets it to GONE. */
+    private Runnable mHideFlyout =
+            () -> mFlyout.animate().alpha(0f).withEndAction(() -> mFlyout.setVisibility(GONE));
+
     private int mBubbleSize;
     private int mBubblePadding;
     private int mExpandedAnimateXDistance;
@@ -131,6 +148,9 @@
     private boolean mIsExpanded;
     private boolean mImeVisible;
 
+    /** Whether the stack is currently being dragged. */
+    private boolean mIsDragging = false;
+
     private BubbleTouchHandler mTouchHandler;
     private BubbleController.BubbleExpandListener mExpandListener;
     private BubbleExpandedView.OnBubbleBlockedListener mBlockedListener;
@@ -221,6 +241,17 @@
         mExpandedViewContainer.setClipChildren(false);
         addView(mExpandedViewContainer);
 
+        mFlyout = mInflater.inflate(R.layout.bubble_flyout, this, false);
+        mFlyout.setVisibility(GONE);
+        mFlyout.animate()
+                .setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
+                .setInterpolator(new AccelerateDecelerateInterpolator());
+        addView(mFlyout);
+
+        mFlyoutText = mFlyout.findViewById(R.id.bubble_flyout_text);
+
+        mFlyoutSpring = new SpringAnimation(mFlyout, DynamicAnimation.TRANSLATION_X);
+
         mExpandedViewXAnim =
                 new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
         mExpandedViewXAnim.setSpring(
@@ -448,6 +479,8 @@
 
         requestUpdate();
         logBubbleEvent(b, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
+
+        animateInFlyoutForBubble(b);
     }
 
     /**
@@ -549,6 +582,7 @@
                 mBubbleContainer.moveViewTo(b.iconView, 0);
             }
             requestUpdate();
+            animateInFlyoutForBubble(b /* bubble */);
         }
         if (mIsExpanded && entry.equals(mExpandedBubble.entry)) {
             entry.setShowInShadeWhenBubble(false);
@@ -577,11 +611,18 @@
             }
             // Outside parts of view we care about.
             return null;
+        } else if (isIntersecting(mFlyout, x, y)) {
+            return mFlyout;
         }
-        // If we're collapsed, the stack is always the target.
+
+        // If it wasn't an individual bubble in the expanded state, or the flyout, it's the stack.
         return this;
     }
 
+    public View getFlyoutView() {
+        return mFlyout;
+    }
+
     /**
      * Collapses the stack of bubbles.
      * <p>
@@ -622,6 +663,8 @@
      */
     private void animateExpansion(boolean shouldExpand) {
         if (mIsExpanded != shouldExpand) {
+            hideFlyoutImmediate();
+
             mIsExpanded = shouldExpand;
             updateExpandedBubble();
             applyCurrentState();
@@ -735,6 +778,9 @@
 
         mStackAnimationController.cancelStackPositionAnimations();
         mBubbleContainer.setController(mStackAnimationController);
+        hideFlyoutImmediate();
+
+        mIsDragging = true;
     }
 
     void onDragged(float x, float y) {
@@ -747,6 +793,7 @@
 
     void onDragFinish(float x, float y, float velX, float velY) {
         // TODO: Add fling to bottom to dismiss.
+        mIsDragging = false;
 
         if (mIsExpanded || mIsExpansionAnimating) {
             return;
@@ -797,6 +844,47 @@
         }
     }
 
+    /**
+     * Animates in the flyout for the given bubble, if available, and then hides it after some time.
+     */
+    @VisibleForTesting
+    void animateInFlyoutForBubble(Bubble bubble) {
+        final CharSequence updateMessage = bubble.entry.getUpdateMessage(getContext());
+
+        // Show the message if one exists, and we're not expanded or animating expansion.
+        if (updateMessage != null && !isExpanded() && !mIsExpansionAnimating && !mIsDragging) {
+            final PointF stackPos = mStackAnimationController.getStackPosition();
+
+            mFlyoutText.setText(updateMessage);
+            mFlyout.measure(WRAP_CONTENT, WRAP_CONTENT);
+            mFlyout.post(() -> {
+                final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
+                final float destinationX = onLeft
+                        ? stackPos.x + mBubbleSize + mBubblePadding
+                        : stackPos.x - mFlyout.getMeasuredWidth();
+
+                // Translate towards the stack slightly, then spring out from the stack.
+                mFlyout.setTranslationX(destinationX + (onLeft ? -mBubblePadding : mBubblePadding));
+                mFlyout.setTranslationY(stackPos.y);
+                mFlyout.setAlpha(0f);
+
+                mFlyout.setVisibility(VISIBLE);
+
+                mFlyout.animate().alpha(1f);
+                mFlyoutSpring.animateToFinalPosition(destinationX);
+
+                mFlyout.removeCallbacks(mHideFlyout);
+                mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
+            });
+        }
+    }
+
+    /** Hide the flyout immediately and cancel any pending hide runnables. */
+    private void hideFlyoutImmediate() {
+        mFlyout.removeCallbacks(mHideFlyout);
+        mHideFlyout.run();
+    }
+
     @Override
     public void getBoundsOnScreen(Rect outRect) {
         if (!mIsExpanded) {
@@ -806,6 +894,12 @@
         } else {
             mBubbleContainer.getBoundsOnScreen(outRect);
         }
+
+        if (mFlyout.getVisibility() == View.VISIBLE) {
+            final Rect flyoutBounds = new Rect();
+            mFlyout.getBoundsOnScreen(flyoutBounds);
+            outRect.union(flyoutBounds);
+        }
     }
 
     private int getStatusBarHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index a7170d0..baeedaa 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -86,6 +86,7 @@
         }
 
         final boolean isStack = mStack.equals(mTouchedView);
+        final boolean isFlyout = mStack.getFlyoutView().equals(mTouchedView);
         final float rawX = event.getRawX();
         final float rawY = event.getRawY();
 
@@ -96,14 +97,18 @@
             case MotionEvent.ACTION_DOWN:
                 trackMovement(event);
 
-                mDismissViewController.createDismissTarget();
-                mHandler.postDelayed(mShowDismissAffordance, SHOW_TARGET_DELAY);
-
                 mTouchDown.set(rawX, rawY);
 
+                if (!isFlyout) {
+                    mDismissViewController.createDismissTarget();
+                    mHandler.postDelayed(mShowDismissAffordance, SHOW_TARGET_DELAY);
+                }
+
                 if (isStack) {
                     mViewPositionOnTouchDown.set(mStack.getStackPosition());
                     mStack.onDragStart();
+                } else if (isFlyout) {
+                    // TODO(b/129768381): Make the flyout dismissable with a gesture.
                 } else {
                     mViewPositionOnTouchDown.set(
                             mTouchedView.getTranslationX(), mTouchedView.getTranslationY());
@@ -123,6 +128,8 @@
                 if (mMovedEnough) {
                     if (isStack) {
                         mStack.onDragged(viewX, viewY);
+                    } else if (isFlyout) {
+                        // TODO(b/129768381): Make the flyout dismissable with a gesture.
                     } else {
                         mStack.onBubbleDragged(mTouchedView, viewX, viewY);
                     }
@@ -141,6 +148,11 @@
                 trackMovement(event);
                 if (mInDismissTarget && isStack) {
                     mController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+                } else if (isFlyout) {
+                    // TODO(b/129768381): Expand if tapped, dismiss if swiped away.
+                    if (!mStack.isExpanded() && !mMovedEnough) {
+                        mStack.expandStack();
+                    }
                 } else if (mMovedEnough) {
                     mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
                     final float velX = mVelocityTracker.getXVelocity();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index 3b9164d..84b86bf 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -27,7 +27,6 @@
 import android.graphics.drawable.InsetDrawable;
 import android.util.AttributeSet;
 import android.widget.FrameLayout;
-import android.widget.TextView;
 
 import com.android.internal.graphics.ColorUtils;
 import com.android.systemui.Interpolators;
@@ -49,7 +48,6 @@
     private Context mContext;
 
     private BadgedImageView mBadgedImageView;
-    private TextView mMessageView;
     private int mPadding;
     private int mIconInset;
 
@@ -78,10 +76,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
-        mBadgedImageView = (BadgedImageView) findViewById(R.id.bubble_image);
-        mMessageView = (TextView) findViewById(R.id.message_view);
-        mMessageView.setVisibility(GONE);
-        mMessageView.setPivotX(0);
+        mBadgedImageView = findViewById(R.id.bubble_image);
     }
 
     @Override
@@ -89,33 +84,6 @@
         super.onAttachedToWindow();
     }
 
-    @Override
-    protected void onMeasure(int widthSpec, int heightSpec) {
-        measureChild(mBadgedImageView, widthSpec, heightSpec);
-        measureChild(mMessageView, widthSpec, heightSpec);
-        boolean messageGone = mMessageView.getVisibility() == GONE;
-        int imageHeight = mBadgedImageView.getMeasuredHeight();
-        int imageWidth = mBadgedImageView.getMeasuredWidth();
-        int messageHeight = messageGone ? 0 : mMessageView.getMeasuredHeight();
-        int messageWidth = messageGone ? 0 : mMessageView.getMeasuredWidth();
-        setMeasuredDimension(
-                getPaddingStart() + imageWidth + mPadding + messageWidth + getPaddingEnd(),
-                getPaddingTop() + Math.max(imageHeight, messageHeight) + getPaddingBottom());
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        left = getPaddingStart();
-        top = getPaddingTop();
-        int imageWidth = mBadgedImageView.getMeasuredWidth();
-        int imageHeight = mBadgedImageView.getMeasuredHeight();
-        int messageWidth = mMessageView.getMeasuredWidth();
-        int messageHeight = mMessageView.getMeasuredHeight();
-        mBadgedImageView.layout(left, top, left + imageWidth, top + imageHeight);
-        mMessageView.layout(left + imageWidth + mPadding, top,
-                left + imageWidth + mPadding + messageWidth, top + messageHeight);
-    }
-
     /**
      * Populates this view with a notification.
      * <p>
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index c395031..78c4fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -157,6 +157,15 @@
         return mStackPosition;
     }
 
+    /** Whether the stack is on the left side of the screen. */
+    public boolean isStackOnLeftSide() {
+        if (mLayout != null) {
+            return mStackPosition.x - mIndividualBubbleSize / 2 < mLayout.getWidth() / 2;
+        } else {
+            return false;
+        }
+    }
+
     /**
      * Flings the stack starting with the given velocities, springing it to the nearest edge
      * afterward.
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index dcacd0f..e22b24e 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -1127,10 +1127,7 @@
         }
 
         protected int getActionLayoutId(Context context) {
-            if (isGridEnabled(context)) {
-                return com.android.systemui.R.layout.global_actions_grid_item;
-            }
-            return com.android.systemui.R.layout.global_actions_item;
+            return com.android.systemui.R.layout.global_actions_grid_item;
         }
 
         public View create(
@@ -1540,26 +1537,28 @@
             initializeLayout();
         }
 
-        private boolean initializePanel() {
+        private boolean shouldUsePanel() {
             if (!isPanelEnabled(mContext) || mPanelController == null) {
                 return false;
             }
-            View panelView = mPanelController.getPanelContent();
-            if (panelView == null) {
+            if (mPanelController.getPanelContent() == null) {
                 return false;
             }
+            return true;
+        }
+
+        private void initializePanel() {
             FrameLayout panelContainer = new FrameLayout(mContext);
             FrameLayout.LayoutParams panelParams =
                     new FrameLayout.LayoutParams(
                             FrameLayout.LayoutParams.MATCH_PARENT,
                             FrameLayout.LayoutParams.WRAP_CONTENT);
-            panelContainer.addView(panelView, panelParams);
+            panelContainer.addView(mPanelController.getPanelContent(), panelParams);
             addContentView(
                     panelContainer,
                     new ViewGroup.LayoutParams(
                             ViewGroup.LayoutParams.MATCH_PARENT,
                             ViewGroup.LayoutParams.MATCH_PARENT));
-            return true;
         }
 
         private void initializeLayout() {
@@ -1578,8 +1577,7 @@
             mGlobalActionsLayout.setRotationListener(this::onRotate);
             mGlobalActionsLayout.setAdapter(mAdapter);
 
-            boolean panelEnabled = initializePanel();
-            if (!panelEnabled) {
+            if (!shouldUsePanel()) {
                 if (mBackgroundDrawable == null) {
                     mBackgroundDrawable = new GradientDrawable(mContext);
                 }
@@ -1589,12 +1587,12 @@
                         com.android.systemui.R.drawable.global_action_panel_scrim);
                 mScrimAlpha = 1f;
             }
-            mGlobalActionsLayout.setSnapToEdge(panelEnabled);
+            mGlobalActionsLayout.setSnapToEdge(true);
             getWindow().setBackgroundDrawable(mBackgroundDrawable);
         }
 
         private int getGlobalActionsLayoutId(Context context) {
-            if (isGridEnabled(context)) {
+            if (isForceGridEnabled(context) || shouldUsePanel()) {
                 if (RotationUtils.getRotation(context) == RotationUtils.ROTATION_SEASCAPE) {
                     return com.android.systemui.R.layout.global_actions_grid_seascape;
                 }
@@ -1653,11 +1651,13 @@
             super.show();
             mShowing = true;
             mBackgroundDrawable.setAlpha(0);
-            mGlobalActionsLayout.setTranslationX(getAnimTranslation());
+            mGlobalActionsLayout.setTranslationX(mGlobalActionsLayout.getAnimationOffsetX());
+            mGlobalActionsLayout.setTranslationY(mGlobalActionsLayout.getAnimationOffsetY());
             mGlobalActionsLayout.setAlpha(0);
             mGlobalActionsLayout.animate()
                     .alpha(1)
                     .translationX(0)
+                    .translationY(0)
                     .setDuration(300)
                     .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                     .setUpdateListener(animation -> {
@@ -1675,10 +1675,12 @@
             }
             mShowing = false;
             mGlobalActionsLayout.setTranslationX(0);
+            mGlobalActionsLayout.setTranslationY(0);
             mGlobalActionsLayout.setAlpha(1);
             mGlobalActionsLayout.animate()
                     .alpha(0)
-                    .translationX(getAnimTranslation())
+                    .translationX(mGlobalActionsLayout.getAnimationOffsetX())
+                    .translationY(mGlobalActionsLayout.getAnimationOffsetY())
                     .setDuration(300)
                     .withEndAction(super::dismiss)
                     .setInterpolator(new LogAccelerateInterpolator())
@@ -1701,11 +1703,6 @@
             }
         }
 
-        private float getAnimTranslation() {
-            return getContext().getResources().getDimension(
-                    com.android.systemui.R.dimen.global_actions_panel_width) / 2;
-        }
-
         @Override
         public void onColorsChanged(ColorExtractor extractor, int which) {
             if (mKeyguardShowing) {
@@ -1731,17 +1728,19 @@
         }
 
         public void onRotate(int from, int to) {
-            if (mShowing && isGridEnabled(mContext)) {
+            if (mShowing && (shouldUsePanel() || isForceGridEnabled(mContext))) {
                 refreshDialog();
             }
         }
     }
 
     /**
-     * Determines whether or not the Global Actions menu should use the newer grid-style layout.
+     * Determines whether or not the Global Actions menu should be forced to
+     * use the newer grid-style layout.
      */
-    private static boolean isGridEnabled(Context context) {
-        return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.GLOBAL_ACTIONS_GRID_ENABLED);
+    private static boolean isForceGridEnabled(Context context) {
+        return FeatureFlagUtils.isEnabled(context,
+                FeatureFlagUtils.FORCE_GLOBAL_ACTIONS_GRID_ENABLED);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
index 9a0759c..f882569 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsGridLayout.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.globalactions;
 
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
+
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -85,8 +89,8 @@
         int rotation = RotationUtils.getRotation(mContext);
 
         boolean reverse = false; // should we add items to parents in the reverse order?
-        if (rotation == RotationUtils.ROTATION_NONE
-                || rotation == RotationUtils.ROTATION_SEASCAPE) {
+        if (rotation == ROTATION_NONE
+                || rotation == ROTATION_SEASCAPE) {
             reverse = !reverse; // if we're in portrait or seascape, reverse items
         }
         if (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())
@@ -125,9 +129,9 @@
     private void updateSnapPosition() {
         if (mSnapToEdge) {
             setPadding(0, 0, 0, 0);
-            if (mRotation == RotationUtils.ROTATION_LANDSCAPE) {
+            if (mRotation == ROTATION_LANDSCAPE) {
                 setGravity(Gravity.RIGHT);
-            } else if (mRotation == RotationUtils.ROTATION_SEASCAPE) {
+            } else if (mRotation == ROTATION_SEASCAPE) {
                 setGravity(Gravity.LEFT);
             } else {
                 setGravity(Gravity.BOTTOM);
@@ -157,9 +161,9 @@
             return getSeparatedView();
         } else {
             switch (rotation) {
-                case RotationUtils.ROTATION_LANDSCAPE:
+                case ROTATION_LANDSCAPE:
                     return getListView().getParentView(index, false, true);
-                case RotationUtils.ROTATION_SEASCAPE:
+                case ROTATION_SEASCAPE:
                     return getListView().getParentView(index, true, true);
                 default:
                     return getListView().getParentView(index, false, false);
@@ -174,4 +178,31 @@
     public void setDivisionView(View v) {
         // do nothing
     }
+
+    private float getAnimationDistance() {
+        int rows = getListView().getRowCount();
+        float gridItemSize = getContext().getResources().getDimension(
+                com.android.systemui.R.dimen.global_actions_grid_item_height);
+        return rows * gridItemSize / 2;
+    }
+
+    @Override
+    public float getAnimationOffsetX() {
+        switch (RotationUtils.getRotation(getContext())) {
+            case ROTATION_LANDSCAPE:
+                return getAnimationDistance();
+            case ROTATION_SEASCAPE:
+                return -getAnimationDistance();
+            default: // Portrait
+                return 0;
+        }
+    }
+
+    @Override
+    public float getAnimationOffsetY() {
+        if (RotationUtils.getRotation(mContext) == ROTATION_NONE) {
+            return getAnimationDistance();
+        }
+        return 0;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
index 048f801..9c71ffc 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ListGridLayout.java
@@ -109,7 +109,10 @@
         }
     }
 
-    private int getRowCount() {
+    /**
+     * Get the number of rows which will be used to render children.
+     */
+    public int getRowCount() {
         // special case for 3 to use a single row
         if (mExpectedCount == 3) {
             return 1;
@@ -117,7 +120,10 @@
         return (int) Math.round(Math.sqrt(mExpectedCount));
     }
 
-    private int getColumnCount() {
+    /**
+     * Get the number of columns which will be used to render children.
+     */
+    public int getColumnCount() {
         // special case for 3 to use a single row
         if (mExpectedCount == 3) {
             return 3;
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 3140e6d..3ec6cb7 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -795,7 +795,10 @@
                 ? mExpandedMovementBounds
                 : mNormalMovementBounds;
         try {
-            mPinnedStackController.setMinEdgeSize(isMenuExpanded ? mExpandedShortestEdgeSize : 0);
+            if (mPinnedStackController != null) {
+                mPinnedStackController.setMinEdgeSize(
+                        isMenuExpanded ? mExpandedShortestEdgeSize : 0);
+            }
         } catch (RemoteException e) {
             Log.e(TAG, "Could not set minimized state", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 10eacba..644664e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -22,7 +22,6 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.graphics.PorterDuff.Mode;
@@ -320,29 +319,13 @@
         mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
                 TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
         final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
-        mMultiUserSwitch.setVisibility(showUserSwitcher(isDemo) ? View.VISIBLE : View.INVISIBLE);
+        mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE);
         mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
         mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
     }
 
-    private boolean showUserSwitcher(boolean isDemo) {
-        if (!mExpanded || isDemo || !UserManager.supportsMultipleUsers()) {
-            return false;
-        }
-        UserManager userManager = UserManager.get(mContext);
-        if (userManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)) {
-            return false;
-        }
-        int switchableUserCount = 0;
-        for (UserInfo user : userManager.getUsers(true)) {
-            if (user.supportsSwitchToByUser()) {
-                ++switchableUserCount;
-                if (switchableUserCount > 1) {
-                    return true;
-                }
-            }
-        }
-        return getResources().getBoolean(R.bool.qs_show_user_switcher_for_single_user);
+    private boolean showUserSwitcher() {
+        return mExpanded && mMultiUserSwitch.isMultiUserEnabled();
     }
 
     private void updateListeners() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 415870c..b1dfbb5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -21,7 +21,7 @@
 import android.app.Dialog;
 import android.content.Context;
 import android.content.Intent;
-import android.media.projection.MediaProjectionInfo;
+import android.media.MediaRouter.RouteInfo;
 import android.provider.Settings;
 import android.service.quicksettings.Tile;
 import android.util.Log;
@@ -48,8 +48,9 @@
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.NetworkController;
 
+import java.util.ArrayList;
 import java.util.LinkedHashMap;
-import java.util.Set;
+import java.util.List;
 
 import javax.inject.Inject;
 
@@ -128,35 +129,30 @@
             return;
         }
 
-        CastDevice activeProjection = getActiveDeviceMediaProjection();
-        if (activeProjection == null) {
-            if (mKeyguard.isSecure() && !mKeyguard.canSkipBouncer()) {
-                mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
-                    showDetail(true);
-                });
-            } else {
+        List<CastDevice> activeDevices = getActiveDevices();
+        // We want to pop up the media route selection dialog if we either have no active devices
+        // (neither routes nor projection), or if we have an active route. In other cases, we assume
+        // that a projection is active. This is messy, but this tile never correctly handled the
+        // case where multiple devices were active :-/.
+        if (activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo)) {
+            mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
                 showDetail(true);
-            }
+            });
         } else {
-            mController.stopCasting(activeProjection);
+            mController.stopCasting(activeDevices.get(0));
         }
     }
 
-    private CastDevice getActiveDeviceMediaProjection() {
-        CastDevice activeDevice = null;
+    private List<CastDevice> getActiveDevices() {
+        ArrayList<CastDevice> activeDevices = new ArrayList<>();
         for (CastDevice device : mController.getCastDevices()) {
             if (device.state == CastDevice.STATE_CONNECTED
                     || device.state == CastDevice.STATE_CONNECTING) {
-                activeDevice = device;
-                break;
+                activeDevices.add(device);
             }
         }
 
-        if (activeDevice != null && activeDevice.tag instanceof MediaProjectionInfo) {
-            return activeDevice;
-        }
-
-        return null;
+        return activeDevices;
     }
 
     @Override
@@ -187,14 +183,18 @@
         state.label = mContext.getString(R.string.quick_settings_cast_title);
         state.contentDescription = state.label;
         state.value = false;
-        final Set<CastDevice> devices = mController.getCastDevices();
+        final List<CastDevice> devices = mController.getCastDevices();
         boolean connecting = false;
+        // We always choose the first device that's in the CONNECTED state in the case where
+        // multiple devices are CONNECTED at the same time.
         for (CastDevice device : devices) {
             if (device.state == CastDevice.STATE_CONNECTED) {
                 state.value = true;
                 state.secondaryLabel = getDeviceName(device);
                 state.contentDescription = state.contentDescription + "," +
                         mContext.getString(R.string.accessibility_cast_name, state.label);
+                connecting = false;
+                break;
             } else if (device.state == CastDevice.STATE_CONNECTING) {
                 connecting = true;
             }
@@ -326,7 +326,7 @@
             return mItems;
         }
 
-        private void updateItems(Set<CastDevice> devices) {
+        private void updateItems(List<CastDevice> devices) {
             if (mItems == null) return;
             Item[] items = null;
             if (devices != null && !devices.isEmpty()) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ead39c69..4d6693f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -24,7 +24,7 @@
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
 import static com.android.systemui.shared.system.NavigationBarCompat.InteractionType;
-import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_CHANNEL;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS;
@@ -38,6 +38,7 @@
 import android.content.ServiceConnection;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.hardware.input.InputManager;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -47,7 +48,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
-import android.view.InputChannel;
+import android.view.InputMonitor;
 import android.view.MotionEvent;
 
 import com.android.internal.policy.ScreenDecorationsUtils;
@@ -58,7 +59,6 @@
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.InputChannelCompat.InputEventDispatcher;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -115,8 +115,6 @@
     private float mWindowCornerRadius;
     private boolean mSupportsRoundedCornersOnWindows;
 
-    private InputEventDispatcher mInputEventDispatcher;
-
     private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
 
         public void startScreenPinning(int taskId) {
@@ -295,6 +293,22 @@
             }
         }
 
+        public Bundle monitorGestureInput(String name, int displayId) {
+            if (!verifyCaller("monitorGestureInput")) {
+                return null;
+            }
+            long token = Binder.clearCallingIdentity();
+            try {
+                InputMonitor monitor =
+                        InputManager.getInstance().monitorGestureInput(name, displayId);
+                Bundle result = new Bundle();
+                result.putParcelable(KEY_EXTRA_INPUT_MONITOR, monitor);
+                return result;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         private boolean verifyCaller(String reason) {
             final int callerId = Binder.getCallingUserHandle().getIdentifier();
             if (callerId != mCurrentBoundedUserId) {
@@ -341,23 +355,16 @@
             } catch (RemoteException e) {
                 Log.e(TAG_OPS, "Lost connection to launcher service", e);
             }
-            try {
-                mOverviewProxy.onBind(mSysUiProxy);
-            } catch (RemoteException e) {
-                mCurrentBoundedUserId = -1;
-                Log.e(TAG_OPS, "Failed to call onBind()", e);
-            }
 
             Bundle params = new Bundle();
             params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
-            params.putParcelable(KEY_EXTRA_INPUT_CHANNEL, createNewInputDispatcher());
             params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
             params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
             try {
                 mOverviewProxy.onInitialize(params);
             } catch (RemoteException e) {
-                // Ignore error until the migration is complete.
-                Log.e(TAG_OPS, "Failed to call onBind()", e);
+                mCurrentBoundedUserId = -1;
+                Log.e(TAG_OPS, "Failed to call onInitialize()", e);
             }
             dispatchNavButtonBounds();
 
@@ -369,7 +376,6 @@
             Log.w(TAG_OPS, "Null binding of '" + name + "', try reconnecting");
             mCurrentBoundedUserId = -1;
             retryConnectionWithBackoff();
-            disposeInputDispatcher();
         }
 
         @Override
@@ -377,32 +383,15 @@
             Log.w(TAG_OPS, "Binding died of '" + name + "', try reconnecting");
             mCurrentBoundedUserId = -1;
             retryConnectionWithBackoff();
-            disposeInputDispatcher();
         }
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
             // Do nothing
             mCurrentBoundedUserId = -1;
-            disposeInputDispatcher();
         }
     };
 
-    private void disposeInputDispatcher() {
-        if (mInputEventDispatcher != null) {
-            mInputEventDispatcher.dispose();
-            mInputEventDispatcher = null;
-        }
-    }
-
-    private InputChannel createNewInputDispatcher() {
-        disposeInputDispatcher();
-
-        InputChannel[] channels = InputChannel.openInputChannelPair("overview-proxy-service");
-        mInputEventDispatcher = new InputEventDispatcher(channels[0], Looper.getMainLooper());
-        return channels[1];
-    }
-
     private final DeviceProvisionedListener mDeviceProvisionedCallback =
                 new DeviceProvisionedListener() {
             @Override
@@ -455,6 +444,17 @@
         }
     }
 
+    public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton,
+            boolean gestureSwipeLeft) {
+        try {
+            if (mOverviewProxy != null) {
+                mOverviewProxy.onBackAction(completed, downX, downY, isButton, gestureSwipeLeft);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG_OPS, "Failed to notify back action", e);
+        }
+    }
+
     /**
      * Sets the navbar region which can receive touch inputs
      */
@@ -567,10 +567,6 @@
         return mOverviewProxy;
     }
 
-    public InputEventDispatcher getInputEventDispatcher() {
-        return mInputEventDispatcher;
-    }
-
     public int getInteractionFlags() {
         return mInteractionFlags;
     }
@@ -633,6 +629,14 @@
         }
     }
 
+    public void notifyAssistantVisibilityChanged(float visibility) {
+        try {
+            mOverviewProxy.onAssistantVisibilityChanged(visibility);
+        } catch (RemoteException e) {
+            Log.e(TAG_OPS, "Failed to call onAssistantVisibilityChanged()", e);
+        }
+    }
+
     private void updateEnabledState() {
         mIsEnabled = mContext.getPackageManager().resolveServiceAsUser(mQuickStepIntent,
                 MATCH_SYSTEM_ONLY,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index a630e49..2793b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -63,6 +63,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Objects;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -142,7 +143,7 @@
             if (DEBUG) {
                 Log.v(TAG, "Notification click handler invoked for intent: " + pendingIntent);
             }
-            logActionClick(view);
+            logActionClick(view, pendingIntent);
             // The intent we are sending is for the application, which
             // won't have permission to immediately start an activity after
             // the user switches to home.  We know it is safe to do at this
@@ -159,11 +160,11 @@
             });
         }
 
-        private void logActionClick(View view) {
+        private void logActionClick(View view, PendingIntent actionIntent) {
             Integer actionIndex = (Integer)
                     view.getTag(com.android.internal.R.id.notification_action_index_tag);
             if (actionIndex == null) {
-                Log.e(TAG, "Couldn't retrieve the actionIndex from the clicked button");
+                // Custom action button, not logging.
                 return;
             }
             ViewParent parent = view.getParent();
@@ -182,8 +183,20 @@
             }
             final int count = mEntryManager.getNotificationData().getActiveNotifications().size();
             final int rank = mEntryManager.getNotificationData().getRank(key);
+
+            // Notification may be updated before this function is executed, and thus play safe
+            // here and verify that the action object is still the one that where the click happens.
+            Notification.Action[] actions = statusBarNotification.getNotification().actions;
+            if (actions == null || actionIndex >= actions.length) {
+                Log.w(TAG, "statusBarNotification.getNotification().actions is null or invalid");
+                return;
+            }
             final Notification.Action action =
                     statusBarNotification.getNotification().actions[actionIndex];
+            if (Objects.equals(action.actionIntent, actionIntent)) {
+                Log.w(TAG, "actionIntent does not match");
+                return;
+            }
             NotificationVisibility.NotificationLocation location =
                     NotificationLogger.getNotificationLocation(
                             mEntryManager.getNotificationData().get(key));
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 f1373d1..f69356e 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
@@ -21,6 +21,7 @@
 import static android.app.Notification.CATEGORY_EVENT;
 import static android.app.Notification.CATEGORY_MESSAGE;
 import static android.app.Notification.CATEGORY_REMINDER;
+import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
@@ -40,6 +41,7 @@
 import android.service.notification.NotificationListenerService;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.view.View;
 import android.widget.ImageView;
@@ -50,6 +52,7 @@
 import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ContrastColorUtil;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.InflationException;
@@ -146,11 +149,6 @@
     private boolean hasSentReply;
 
     /**
-     * Whether this notification should be displayed as a bubble.
-     */
-    private boolean mIsBubble;
-
-    /**
      * Whether this notification has been approved globally, at the app level, and at the channel
      * level for bubbling.
      */
@@ -222,12 +220,8 @@
         this.mHighPriority = highPriority;
     }
 
-    public void setIsBubble(boolean bubbleable) {
-        mIsBubble = bubbleable;
-    }
-
     public boolean isBubble() {
-        return mIsBubble;
+        return (notification.getNotification().flags & FLAG_BUBBLE) != 0;
     }
 
     public void setBubbleDismissed(boolean userDismissed) {
@@ -401,6 +395,72 @@
     }
 
     /**
+     * Returns our best guess for the most relevant text summary of the latest update to this
+     * notification, based on its type. Returns null if there should not be an update message.
+     */
+    public CharSequence getUpdateMessage(Context context) {
+        final Notification underlyingNotif = notification.getNotification();
+        final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
+
+        try {
+            if (Notification.BigTextStyle.class.equals(style)) {
+                // Return the big text, it is big so probably important. If it's not there use the
+                // normal text.
+                CharSequence bigText =
+                        underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
+                return !TextUtils.isEmpty(bigText)
+                        ? bigText
+                        : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+            } else if (Notification.MessagingStyle.class.equals(style)) {
+                final List<Notification.MessagingStyle.Message> messages =
+                        Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+                                (Parcelable[]) underlyingNotif.extras.get(
+                                        Notification.EXTRA_MESSAGES));
+
+                final Notification.MessagingStyle.Message latestMessage =
+                        Notification.MessagingStyle.findLatestIncomingMessage(messages);
+
+                if (latestMessage != null) {
+                    final CharSequence personName = latestMessage.getSenderPerson() != null
+                            ? latestMessage.getSenderPerson().getName()
+                            : null;
+
+                    // Prepend the sender name if available since group chats also use messaging
+                    // style.
+                    if (!TextUtils.isEmpty(personName)) {
+                        return context.getResources().getString(
+                                R.string.notification_summary_message_format,
+                                personName,
+                                latestMessage.getText());
+                    } else {
+                        return latestMessage.getText();
+                    }
+                }
+            } else if (Notification.InboxStyle.class.equals(style)) {
+                CharSequence[] lines =
+                        underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
+
+                // Return the last line since it should be the most recent.
+                if (lines != null && lines.length > 0) {
+                    return lines[lines.length - 1];
+                }
+            } else if (Notification.MediaStyle.class.equals(style)) {
+                // Return nothing, media updates aren't typically useful as a text update.
+                return null;
+            } else {
+                // Default to text extra.
+                return underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
+            }
+        } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
+            // No use crashing, we'll just return null and the caller will assume there's no update
+            // message.
+            e.printStackTrace();
+        }
+
+        return null;
+    }
+
+    /**
      * Abort all existing inflation tasks
      */
     public void abortTask() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
index 580e702..2c15e87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
@@ -85,11 +85,13 @@
         // - User sentiment is negative (DEBUG flag can bypass)
         // - The notification shade is fully expanded (guarantees we're not touching a HUN).
         // - The row is blockable (i.e. not non-blockable)
-        // - The dismissed row is a valid group (>1 or 0 children) or the only child in the group
+        // - The dismissed row is a valid group (>1 or 0 children from the same channel)
+        // or the only child in the group
         if ((row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE || DEBUG)
                 && mIsShadeExpanded
                 && !row.getIsNonblockable()
-                && (!row.isChildInGroup() || row.isOnlyChildInGroup())) {
+                && ((!row.isChildInGroup() || row.isOnlyChildInGroup())
+                        && row.getNumUniqueChannels() <= 1)) {
             // Dismiss any current blocking helper before continuing forward (only one can be shown
             // at a given time).
             dismissCurrentBlockingHelper();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 54bdaa2..69e6120 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -309,7 +309,6 @@
                 mDeviceProvisionedController.isDeviceProvisioned(),
                 row.getIsNonblockable(),
                 isForBlockingHelper,
-                row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE,
                 row.getEntry().importance,
                 row.getEntry().isHighPriority());
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 2e4325b..622b869 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -39,6 +39,7 @@
 import android.content.pm.ResolveInfo;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
 import android.metrics.LogMaker;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -82,9 +83,13 @@
 
     public static final int ACTION_NONE = 0;
     static final int ACTION_UNDO = 1;
+    // standard controls
     static final int ACTION_TOGGLE_SILENT = 2;
+    // unused
     static final int ACTION_BLOCK = 3;
+    // blocking helper
     static final int ACTION_DELIVER_SILENTLY = 4;
+    // standard controls
     private static final int ACTION_ALERT = 5;
 
     private INotificationManager mINotificationManager;
@@ -99,7 +104,6 @@
     private NotificationChannel mSingleNotificationChannel;
     private int mStartingChannelImportance;
     private boolean mWasShownHighPriority;
-    private int mNotificationBlockState = ACTION_NONE;
     /**
      * The last importance level chosen by the user.  Null if the user has not chosen an importance
      * level; non-null once the user takes an action which indicates an explicit preference.
@@ -109,13 +113,13 @@
     private boolean mIsNonblockable;
     private StatusBarNotification mSbn;
     private AnimatorSet mExpandAnimation;
-    private boolean mIsForeground;
     private boolean mIsDeviceProvisioned;
 
     private CheckSaveListener mCheckSaveListener;
     private OnSettingsClickListener mOnSettingsClickListener;
     private OnAppSettingsClickListener mAppSettingsClickListener;
     private NotificationGuts mGutsContainer;
+    private GradientDrawable mSelectedBackground;
 
     /** Whether this view is being shown as part of the blocking helper. */
     private boolean mIsForBlockingHelper;
@@ -125,40 +129,39 @@
      */
     private String mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
 
-    private OnClickListener mOnKeepShowing = v -> {
-        mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
-        if (mIsForBlockingHelper) {
-            closeControls(v);
-            mMetricsLogger.write(getLogMaker().setCategory(
-                    MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
-                    .setType(MetricsEvent.TYPE_ACTION)
-                    .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_STAY_SILENT));
-        }
-    };
-
+    // used by standard ui
     private OnClickListener mOnAlert = v -> {
         mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
         mChosenImportance = IMPORTANCE_DEFAULT;
-        updateButtonsAndHelpText(ACTION_ALERT);
+        updateButtons(ACTION_ALERT);
     };
 
+    // used by standard ui
+    private OnClickListener mOnSilent = v -> {
+        mExitReason = NotificationCounters.BLOCKING_HELPER_DELIVER_SILENTLY;
+        mChosenImportance = IMPORTANCE_LOW;
+        updateButtons(ACTION_TOGGLE_SILENT);
+    };
+
+    // used by standard ui
     private OnClickListener mOnDismissSettings = v -> {
         closeControls(v);
     };
 
+    // used by blocking helper
+    private OnClickListener mOnKeepShowing = v -> {
+        mExitReason = NotificationCounters.BLOCKING_HELPER_KEEP_SHOWING;
+        closeControls(v);
+        mMetricsLogger.write(getLogMaker().setCategory(
+                MetricsEvent.NOTIFICATION_BLOCKING_HELPER)
+                .setType(MetricsEvent.TYPE_ACTION)
+                .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_STAY_SILENT));
+    };
+
+    // used by blocking helper
     private OnClickListener mOnDeliverSilently = v -> {
         handleSaveImportance(
                 ACTION_DELIVER_SILENTLY, MetricsEvent.BLOCKING_HELPER_CLICK_STAY_SILENT);
-        if (!mIsForBlockingHelper) {
-            updateButtonsAndHelpText(ACTION_DELIVER_SILENTLY);
-        }
-    };
-
-    private OnClickListener mOnStopOrMinimizeNotifications = v -> {
-        handleSaveImportance(ACTION_BLOCK, MetricsEvent.BLOCKING_HELPER_CLICK_BLOCKED);
-        if (!mIsForBlockingHelper) {
-            updateButtonsAndHelpText(ACTION_BLOCK);
-        }
     };
 
     private void handleSaveImportance(int action, int metricsSubtype) {
@@ -189,6 +192,7 @@
                     .setType(MetricsEvent.TYPE_DISMISS)
                     .setSubtype(MetricsEvent.BLOCKING_HELPER_CLICK_UNDO));
         } else {
+            // TODO: this can't happen?
             mMetricsLogger.write(importanceChangeLogMaker().setType(MetricsEvent.TYPE_DISMISS));
         }
         saveImportanceAndExitReason(ACTION_UNDO);
@@ -233,7 +237,7 @@
         bindNotification(pm, iNotificationManager, pkg, notificationChannel,
                 numUniqueChannelsInRow, sbn, checkSaveListener, onSettingsClick,
                 onAppSettingsClick, isDeviceProvisioned, isNonblockable,
-                false /* isBlockingHelper */, false /* isUserSentimentNegative */,
+                false /* isBlockingHelper */,
                 importance, wasShownHighPriority);
     }
 
@@ -250,7 +254,6 @@
             boolean isDeviceProvisioned,
             boolean isNonblockable,
             boolean isForBlockingHelper,
-            boolean isUserSentimentNegative,
             int importance,
             boolean wasShownHighPriority)
             throws RemoteException {
@@ -268,13 +271,20 @@
         mStartingChannelImportance = mSingleNotificationChannel.getImportance();
         mWasShownHighPriority = wasShownHighPriority;
         mIsNonblockable = isNonblockable;
-        mIsForeground =
-                (mSbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
         mIsForBlockingHelper = isForBlockingHelper;
         mAppUid = mSbn.getUid();
         mDelegatePkg = mSbn.getOpPkg();
         mIsDeviceProvisioned = isDeviceProvisioned;
 
+        mSelectedBackground = new GradientDrawable();
+        mSelectedBackground.setShape(GradientDrawable.RECTANGLE);
+        mSelectedBackground.setColor(mContext.getColor(R.color.notification_guts_selection_bg));
+        final float cornerRadii = getResources().getDisplayMetrics().density * 8;
+        mSelectedBackground.setCornerRadii(new float[]{cornerRadii, cornerRadii, cornerRadii,
+                cornerRadii, cornerRadii, cornerRadii, cornerRadii, cornerRadii});
+        mSelectedBackground.setStroke((int) (getResources().getDisplayMetrics().density * 2),
+                mContext.getColor(R.color.notification_guts_selection_border));
+
         int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
                 pkg, mAppUid, false /* includeDeleted */);
         if (mNumUniqueChannelsInRow == 0) {
@@ -288,13 +298,74 @@
         }
 
         bindHeader();
-        bindPrompt();
-        bindButtons();
+        bindChannelDetails();
+
+        if (mIsForBlockingHelper) {
+            bindBlockingHelper();
+        } else {
+            bindInlineControls();
+        }
 
         mMetricsLogger.write(notificationControlsLogMaker());
     }
 
-    private void bindHeader() throws RemoteException {
+    private void bindBlockingHelper() {
+        findViewById(R.id.inline_controls).setVisibility(GONE);
+        findViewById(R.id.blocking_helper).setVisibility(VISIBLE);
+
+        findViewById(R.id.undo).setOnClickListener(mOnUndo);
+
+        View turnOffButton = findViewById(R.id.blocking_helper_turn_off_notifications);
+        turnOffButton.setOnClickListener(getSettingsOnClickListener());
+        turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() ? VISIBLE : GONE);
+
+        TextView keepShowing = findViewById(R.id.keep_showing);
+        keepShowing.setOnClickListener(mOnKeepShowing);
+
+        View deliverSilently = findViewById(R.id.deliver_silently);
+        deliverSilently.setOnClickListener(mOnDeliverSilently);
+    }
+
+    private void bindInlineControls() {
+        findViewById(R.id.inline_controls).setVisibility(VISIBLE);
+        findViewById(R.id.blocking_helper).setVisibility(GONE);
+
+        if (mIsNonblockable) {
+            findViewById(R.id.non_configurable_text).setVisibility(VISIBLE);
+            findViewById(R.id.non_configurable_multichannel_text).setVisibility(GONE);
+            findViewById(R.id.interruptiveness_settings).setVisibility(GONE);
+        } else if (mNumUniqueChannelsInRow > 1) {
+            findViewById(R.id.non_configurable_text).setVisibility(GONE);
+            findViewById(R.id.interruptiveness_settings).setVisibility(GONE);
+            findViewById(R.id.non_configurable_multichannel_text).setVisibility(VISIBLE);
+        } else {
+            findViewById(R.id.non_configurable_text).setVisibility(GONE);
+            findViewById(R.id.non_configurable_multichannel_text).setVisibility(GONE);
+            findViewById(R.id.interruptiveness_settings).setVisibility(VISIBLE);
+        }
+
+        View turnOffButton = findViewById(R.id.turn_off_notifications);
+        turnOffButton.setOnClickListener(getSettingsOnClickListener());
+        turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() && !mIsNonblockable
+                ? VISIBLE : GONE);
+
+        View done = findViewById(R.id.done);
+        done.setOnClickListener(mOnDismissSettings);
+
+
+        View silent = findViewById(R.id.silent_row);
+        View alert = findViewById(R.id.alert_row);
+        silent.setOnClickListener(mOnSilent);
+        alert.setOnClickListener(mOnAlert);
+
+        if (mWasShownHighPriority) {
+            updateButtons(ACTION_ALERT);
+        } else {
+            updateButtons(ACTION_TOGGLE_SILENT);
+        }
+    }
+
+    private void bindHeader() {
         // Package name
         Drawable pkgicon = null;
         ApplicationInfo info;
@@ -319,31 +390,44 @@
         // Delegate
         bindDelegate();
 
-        // Settings button.
-        final View settingsButton = findViewById(R.id.info);
-        if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
-            settingsButton.setVisibility(View.VISIBLE);
-            final int appUidF = mAppUid;
-            settingsButton.setOnClickListener(
-                    (View view) -> {
-                        logBlockingHelperCounter(
-                                NotificationCounters.BLOCKING_HELPER_NOTIF_SETTINGS);
-                        mOnSettingsClickListener.onClick(view,
-                                mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel,
-                                appUidF);
-                    });
+        // Set up app settings link (i.e. Customize)
+        View settingsLinkView = findViewById(R.id.app_settings);
+        Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName,
+                mSingleNotificationChannel,
+                mSbn.getId(), mSbn.getTag());
+        if (settingsIntent != null
+                && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
+            settingsLinkView.setVisibility(VISIBLE);
+            settingsLinkView.setOnClickListener((View view) -> {
+                mAppSettingsClickListener.onClick(view, settingsIntent);
+            });
         } else {
-            settingsButton.setVisibility(View.GONE);
+            settingsLinkView.setVisibility(View.GONE);
         }
+
+        // System Settings button.
+        final View settingsButton = findViewById(R.id.info);
+        settingsButton.setOnClickListener(getSettingsOnClickListener());
+        settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
     }
 
-    private void bindPrompt() throws RemoteException {
-        final TextView blockPrompt = findViewById(R.id.block_prompt);
+    private OnClickListener getSettingsOnClickListener() {
+        if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
+            final int appUidF = mAppUid;
+            return ((View view) -> {
+                logBlockingHelperCounter(
+                        NotificationCounters.BLOCKING_HELPER_NOTIF_SETTINGS);
+                mOnSettingsClickListener.onClick(view,
+                        mNumUniqueChannelsInRow > 1 ? null : mSingleNotificationChannel,
+                        appUidF);
+            });
+        }
+        return null;
+    }
+
+    private void bindChannelDetails() throws RemoteException {
         bindName();
         bindGroup();
-        if (mIsNonblockable) {
-            blockPrompt.setText(R.string.notification_unblockable_desc);
-        }
     }
 
     private void bindName() {
@@ -450,110 +534,17 @@
         }
     }
 
-    private void bindButtons() {
-        findViewById(R.id.undo).setOnClickListener(mOnUndo);
-
-        boolean showInterruptivenessSettings =
-                !mIsNonblockable
-                        && !mIsForeground
-                        && !mIsForBlockingHelper
-                        && NotificationUtils.useNewInterruptionModel(mContext);
-        if (showInterruptivenessSettings) {
-            findViewById(R.id.block_or_minimize).setVisibility(GONE);
-            findViewById(R.id.interruptiveness_settings).setVisibility(VISIBLE);
-            View done = findViewById(R.id.done_button);
-            done.setOnClickListener(mOnDismissSettings);
-            View block = findViewById(R.id.int_block_wrapper);
-            View silent = findViewById(R.id.int_silent_wrapper);
-            View alert = findViewById(R.id.int_alert_wrapper);
-            block.setOnClickListener(mOnStopOrMinimizeNotifications);
-            silent.setOnClickListener(mOnDeliverSilently);
-            alert.setOnClickListener(mOnAlert);
-            if (mNotificationBlockState != ACTION_NONE) {
-                updateButtonsAndHelpText(mNotificationBlockState);
-            } else if (mWasShownHighPriority) {
-                updateButtonsAndHelpText(ACTION_ALERT);
-            } else {
-                updateButtonsAndHelpText(ACTION_DELIVER_SILENTLY);
-            }
-        } else {
-            findViewById(R.id.block_or_minimize).setVisibility(VISIBLE);
-            findViewById(R.id.interruptiveness_settings).setVisibility(GONE);
-            View block = findViewById(R.id.block);
-            TextView done = findViewById(R.id.done);
-            View minimize = findViewById(R.id.minimize);
-            View deliverSilently = findViewById(R.id.deliver_silently);
-
-
-            block.setOnClickListener(mOnStopOrMinimizeNotifications);
-            done.setOnClickListener(mOnKeepShowing);
-            minimize.setOnClickListener(mOnStopOrMinimizeNotifications);
-            deliverSilently.setOnClickListener(mOnDeliverSilently);
-
-            if (mIsNonblockable) {
-                done.setText(android.R.string.ok);
-                block.setVisibility(GONE);
-                minimize.setVisibility(GONE);
-                deliverSilently.setVisibility(GONE);
-            } else if (mIsForeground) {
-                block.setVisibility(GONE);
-                minimize.setVisibility(VISIBLE);
-            } else {
-                block.setVisibility(VISIBLE);
-                minimize.setVisibility(GONE);
-            }
-
-            // Set up app settings link (i.e. Customize)
-            View settingsLinkView = findViewById(R.id.app_settings);
-            Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName,
-                    mSingleNotificationChannel,
-                    mSbn.getId(), mSbn.getTag());
-            if (!mIsForBlockingHelper
-                    && settingsIntent != null
-                    && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
-                settingsLinkView.setVisibility(VISIBLE);
-                settingsLinkView.setOnClickListener((View view) -> {
-                    mAppSettingsClickListener.onClick(view, settingsIntent);
-                });
-            } else {
-                settingsLinkView.setVisibility(View.GONE);
-            }
-        }
-    }
-
-    private void updateButtonsAndHelpText(int blockState) {
-        mNotificationBlockState = blockState;
-        ImageView block = findViewById(R.id.int_block);
-        ImageView silent = findViewById(R.id.int_silent);
-        ImageView alert = findViewById(R.id.int_alert);
-        TextView hintText = findViewById(R.id.hint_text);
+    private void updateButtons(int blockState) {
+        View silent = findViewById(R.id.silent_row);
+        View alert = findViewById(R.id.alert_row);
         switch (blockState) {
-            case ACTION_BLOCK:
-                block.setBackgroundResource(R.drawable.circle_blue_40dp);
-                block.setColorFilter(Color.WHITE);
-                silent.setBackgroundResource(R.drawable.circle_white_40dp);
-                silent.setColorFilter(getResources().getColor(R.color.GM2_grey_400));
-                alert.setBackgroundResource(R.drawable.circle_white_40dp);
-                alert.setColorFilter(getResources().getColor(R.color.GM2_grey_400));
-                hintText.setText(R.string.hint_text_block);
-                break;
-            case ACTION_DELIVER_SILENTLY:
-                silent.setBackgroundResource(R.drawable.circle_blue_40dp);
-                silent.setColorFilter(Color.WHITE);
-                block.setBackgroundResource(R.drawable.circle_white_40dp);
-                block.setColorFilter(getResources().getColor(R.color.GM2_grey_400));
-                alert.setBackgroundResource(R.drawable.circle_white_40dp);
-                alert.setColorFilter(getResources().getColor(R.color.GM2_grey_400));
-                hintText.setText(R.string.hint_text_silent);
+            case ACTION_TOGGLE_SILENT:
+                silent.setBackground(mSelectedBackground);
+                alert.setBackground(null);
                 break;
             case ACTION_ALERT:
-                alert.setBackgroundResource(R.drawable.circle_blue_40dp);
-                alert.setColorFilter(Color.WHITE);
-                block.setBackgroundResource(R.drawable.circle_white_40dp);
-                block.setColorFilter(getResources().getColor(R.color.GM2_grey_400));
-                silent.setBackgroundResource(R.drawable.circle_white_40dp);
-                silent.setColorFilter(getResources().getColor(R.color.GM2_grey_400));
-                hintText.setText(R.string.hint_text_alert);
+                alert.setBackground(mSelectedBackground);
+                silent.setBackground(null);
                 break;
         }
     }
@@ -575,28 +566,20 @@
                     mChosenImportance = IMPORTANCE_DEFAULT;
                 }
                 break;
-            case ACTION_BLOCK:
-                mExitReason = NotificationCounters.BLOCKING_HELPER_STOP_NOTIFICATIONS;
-                if (mIsForeground) {
-                    mChosenImportance = IMPORTANCE_MIN;
-                } else {
-                    mChosenImportance = IMPORTANCE_NONE;
-                }
-                break;
             default:
                 throw new IllegalArgumentException();
         }
     }
 
+    // only used for blocking helper
     private void swapContent(@NotificationInfoAction int action, boolean animate) {
         if (mExpandAnimation != null) {
             mExpandAnimation.cancel();
         }
 
-        View prompt = findViewById(R.id.prompt);
+        View blockingHelper = findViewById(R.id.blocking_helper);
         ViewGroup confirmation = findViewById(R.id.confirmation);
         TextView confirmationText = findViewById(R.id.confirmation_text);
-        View header = findViewById(R.id.header);
 
         saveImportanceAndExitReason(action);
 
@@ -606,33 +589,20 @@
             case ACTION_DELIVER_SILENTLY:
                 confirmationText.setText(R.string.notification_channel_silenced);
                 break;
-            case ACTION_TOGGLE_SILENT:
-                if (mWasShownHighPriority) {
-                    confirmationText.setText(R.string.notification_channel_silenced);
-                } else {
-                    confirmationText.setText(R.string.notification_channel_unsilenced);
-                }
-                break;
-            case ACTION_BLOCK:
-                if (mIsForeground) {
-                    confirmationText.setText(R.string.notification_channel_minimized);
-                } else {
-                    confirmationText.setText(R.string.notification_channel_disabled);
-                }
-                break;
             default:
                 throw new IllegalArgumentException();
         }
 
         boolean isUndo = action == ACTION_UNDO;
 
-        prompt.setVisibility(isUndo ? VISIBLE : GONE);
+        blockingHelper.setVisibility(isUndo ? VISIBLE : GONE);
+        findViewById(R.id.channel_info).setVisibility(isUndo ? VISIBLE : GONE);
+        findViewById(R.id.header).setVisibility(isUndo ? VISIBLE : GONE);
         confirmation.setVisibility(isUndo ? GONE : VISIBLE);
-        header.setVisibility(isUndo ? VISIBLE : GONE);
 
         if (animate) {
-            ObjectAnimator promptAnim = ObjectAnimator.ofFloat(prompt, View.ALPHA,
-                    prompt.getAlpha(), isUndo ? 1f : 0f);
+            ObjectAnimator promptAnim = ObjectAnimator.ofFloat(blockingHelper, View.ALPHA,
+                    blockingHelper.getAlpha(), isUndo ? 1f : 0f);
             promptAnim.setInterpolator(isUndo ? Interpolators.ALPHA_IN : Interpolators.ALPHA_OUT);
             ObjectAnimator confirmAnim = ObjectAnimator.ofFloat(confirmation, View.ALPHA,
                     confirmation.getAlpha(), isUndo ? 0f : 1f);
@@ -652,7 +622,7 @@
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     if (!mCancelled) {
-                        prompt.setVisibility(isUndo ? VISIBLE : GONE);
+                        blockingHelper.setVisibility(isUndo ? VISIBLE : GONE);
                         confirmation.setVisibility(isUndo ? GONE : VISIBLE);
                     }
                 }
@@ -674,15 +644,11 @@
         }
         mExitReason = NotificationCounters.BLOCKING_HELPER_DISMISSED;
 
-        View prompt = findViewById(R.id.prompt);
-        ViewGroup confirmation = findViewById(R.id.confirmation);
-        View header = findViewById(R.id.header);
-        prompt.setVisibility(VISIBLE);
-        prompt.setAlpha(1f);
-        confirmation.setVisibility(GONE);
-        confirmation.setAlpha(1f);
-        header.setVisibility(VISIBLE);
-        header.setAlpha(1f);
+        if (mIsForBlockingHelper) {
+            bindBlockingHelper();
+        } else {
+            bindInlineControls();
+        }
 
         mMetricsLogger.write(notificationControlsLogMaker().setType(MetricsEvent.TYPE_CLOSE));
     }
@@ -730,8 +696,8 @@
      * commit the updated importance.
      *
      * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
-     * user does not have the ability to undo the action anymore. See {@link #swapContent(boolean)}
-     * for where undo is handled.
+     * user does not have the ability to undo the action anymore. See
+     * {@link #swapContent(boolean, boolean)} for where undo is handled.
      */
     @VisibleForTesting
     void closeControls(View v) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index ca45209..2e85fea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -414,7 +414,9 @@
             return MODE_WAKE_AND_UNLOCK_FROM_DREAM;
         }
         if (mStatusBarKeyguardViewManager.isShowing()) {
-            if (mStatusBarKeyguardViewManager.isBouncerShowing() && unlockingAllowed) {
+            if ((mStatusBarKeyguardViewManager.isBouncerShowing()
+                    || mStatusBarKeyguardViewManager.isBouncerPartiallyVisible())
+                    && unlockingAllowed) {
                 return MODE_DISMISS_BOUNCER;
             } else if (unlockingAllowed) {
                 return faceStayingOnKeyguard ? MODE_ONLY_WAKE : MODE_UNLOCK;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
new file mode 100644
index 0000000..212666f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -0,0 +1,358 @@
+/**
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.hardware.input.InputManager;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.Choreographer;
+import android.view.Gravity;
+import android.view.IPinnedStackController;
+import android.view.IPinnedStackListener;
+import android.view.ISystemGestureExclusionListener;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputMonitor;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+
+import com.android.systemui.R;
+import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Utility class to handle edge swipes for back gesture
+ */
+public class EdgeBackGestureHandler implements DisplayListener {
+
+    private static final String TAG = "EdgeBackGestureHandler";
+
+    private final IPinnedStackListener.Stub mImeChangedListener = new IPinnedStackListener.Stub() {
+        @Override
+        public void onListenerRegistered(IPinnedStackController controller) {
+        }
+
+        @Override
+        public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+            // No need to thread jump, assignments are atomic
+            mImeHeight = imeVisible ? imeHeight : 0;
+            // TODO: Probably cancel any existing gesture
+        }
+
+        @Override
+        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
+        }
+
+        @Override
+        public void onMinimizedStateChanged(boolean isMinimized) {
+        }
+
+        @Override
+        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
+                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
+                int displayRotation) {
+        }
+
+        @Override
+        public void onActionsChanged(ParceledListSlice actions) {
+        }
+    };
+
+    private ISystemGestureExclusionListener mGestureExclusionListener =
+            new ISystemGestureExclusionListener.Stub() {
+                @Override
+                public void onSystemGestureExclusionChanged(int displayId,
+                        Region systemGestureExclusion) {
+                    if (displayId == mDisplayId) {
+                        mMainExecutor.execute(() -> mExcludeRegion.set(systemGestureExclusion));
+                    }
+                }
+            };
+
+    private final Context mContext;
+    private final OverviewProxyService mOverviewProxyService;
+
+    private final Point mDisplaySize = new Point();
+    private final int mDisplayId;
+
+    private final Executor mMainExecutor;
+
+    private final Region mExcludeRegion = new Region();
+    // The edge width where touch down is allowed
+    private final int mEdgeWidth;
+    // The slop to distinguish between horizontal and vertical motion
+    private final float mTouchSlop;
+    // Minimum distance to move so that is can be considerd as a back swipe
+    private final float mSwipeThreshold;
+
+    private final int mNavBarHeight;
+
+    private final PointF mDownPoint = new PointF();
+    private boolean mThresholdCrossed = false;
+    private boolean mIgnoreThisGesture = false;
+    private boolean mIsOnLeftEdge;
+
+    private int mImeHeight = 0;
+
+    private boolean mIsAttached;
+    private boolean mIsGesturalModeEnabled;
+    private boolean mIsEnabled;
+
+    private InputMonitor mInputMonitor;
+    private InputEventReceiver mInputEventReceiver;
+
+    private final WindowManager mWm;
+
+    private NavigationBarEdgePanel mEdgePanel;
+    private WindowManager.LayoutParams mEdgePanelLp;
+
+    public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService) {
+        mContext = context;
+        mDisplayId = context.getDisplayId();
+        mMainExecutor = context.getMainExecutor();
+        mWm = context.getSystemService(WindowManager.class);
+        mOverviewProxyService = overviewProxyService;
+
+        mEdgeWidth = QuickStepContract.getEdgeSensitivityWidth(context);
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mSwipeThreshold = context.getResources()
+                .getDimension(R.dimen.navigation_edge_action_drag_threshold);
+
+        mNavBarHeight = context.getResources().getDimensionPixelSize(R.dimen.navigation_bar_height);
+    }
+
+    /**
+     * @see NavigationBarView#onAttachedToWindow()
+     */
+    public void onNavBarAttached() {
+        mIsAttached = true;
+        onOverlaysChanged();
+    }
+
+    /**
+     * @see NavigationBarView#onDetachedFromWindow()
+     */
+    public void onNavBarDetached() {
+        mIsAttached = false;
+        updateIsEnabled();
+    }
+
+    /**
+     * Called when system overlays has changed
+     */
+    public void onOverlaysChanged() {
+        mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mContext);
+        updateIsEnabled();
+    }
+
+    private void disposeInputChannel() {
+        if (mInputEventReceiver != null) {
+            mInputEventReceiver.dispose();
+            mInputEventReceiver = null;
+        }
+        if (mInputMonitor != null) {
+            mInputMonitor.dispose();
+            mInputMonitor = null;
+        }
+    }
+
+    private void updateIsEnabled() {
+        boolean isEnabled = mIsAttached && mIsGesturalModeEnabled;
+        if (isEnabled == mIsEnabled) {
+            return;
+        }
+        mIsEnabled = isEnabled;
+        disposeInputChannel();
+
+        if (mEdgePanel != null) {
+            mWm.removeView(mEdgePanel);
+            mEdgePanel = null;
+        }
+
+        if (!mIsEnabled) {
+            WindowManagerWrapper.getInstance().removePinnedStackListener(mImeChangedListener);
+
+            try {
+                WindowManagerGlobal.getWindowManagerService()
+                        .unregisterSystemGestureExclusionListener(
+                                mGestureExclusionListener, mDisplayId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to unregister window manager callbacks", e);
+            }
+
+        } else {
+            updateDisplaySize();
+
+            try {
+                WindowManagerWrapper.getInstance().addPinnedStackListener(mImeChangedListener);
+                WindowManagerGlobal.getWindowManagerService()
+                        .registerSystemGestureExclusionListener(
+                                mGestureExclusionListener, mDisplayId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to register window manager callbacks", e);
+            }
+
+            // Register input event receiver
+            mInputMonitor = InputManager.getInstance().monitorGestureInput(
+                    "edge-swipe", mDisplayId);
+            mInputEventReceiver = new InputEventReceiver(mInputMonitor.getInputChannel(),
+                    Looper.getMainLooper(), Choreographer.getMainThreadInstance(),
+                    this::onInputEvent);
+
+            // Add a nav bar panel window
+            mEdgePanel = new NavigationBarEdgePanel(mContext);
+            mEdgePanelLp = new WindowManager.LayoutParams(
+                    mContext.getResources()
+                            .getDimensionPixelSize(R.dimen.navigation_edge_panel_width),
+                    mContext.getResources()
+                            .getDimensionPixelSize(R.dimen.navigation_edge_panel_height),
+                    WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                            | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+                            | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+                    PixelFormat.TRANSLUCENT);
+            mEdgePanelLp.setTitle(TAG + mDisplayId);
+            mEdgePanelLp.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
+            mEdgePanelLp.windowAnimations = 0;
+            mEdgePanel.setLayoutParams(mEdgePanelLp);
+            mWm.addView(mEdgePanel, mEdgePanelLp);
+        }
+    }
+
+    private void onInputEvent(InputEvent ev) {
+        if (ev instanceof MotionEvent) {
+            onMotionEvent((MotionEvent) ev);
+        }
+    }
+
+    private boolean isWithinTouchRegion(int x, int y) {
+        if (y > (mDisplaySize.y - Math.max(mImeHeight, mNavBarHeight))) {
+            return false;
+        }
+
+        if (x > mEdgeWidth && x < (mDisplaySize.x - mEdgeWidth)) {
+            return false;
+        }
+        return !mExcludeRegion.contains(x, y);
+    }
+
+    private void onMotionEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            // Verify if this is in within the touch region
+            mIgnoreThisGesture = !isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
+            if (!mIgnoreThisGesture) {
+                mIsOnLeftEdge = ev.getX() < mEdgeWidth;
+                mEdgePanelLp.gravity = mIsOnLeftEdge
+                        ? (Gravity.LEFT | Gravity.TOP)
+                        : (Gravity.RIGHT | Gravity.TOP);
+                mEdgePanel.setIsLeftPanel(mIsOnLeftEdge);
+                mEdgePanelLp.y = MathUtils.constrain(
+                        (int) (ev.getY() - mEdgePanelLp.height / 2),
+                        0, mDisplaySize.y);
+                mWm.updateViewLayout(mEdgePanel, mEdgePanelLp);
+
+                mDownPoint.set(ev.getX(), ev.getY());
+                mThresholdCrossed = false;
+                mEdgePanel.handleTouch(ev);
+            }
+        } else if (!mIgnoreThisGesture) {
+            if (!mThresholdCrossed && ev.getAction() == MotionEvent.ACTION_MOVE) {
+                float dx = Math.abs(ev.getX() - mDownPoint.x);
+                float dy = Math.abs(ev.getY() - mDownPoint.y);
+                if (dy > dx && dy > mTouchSlop) {
+                    // Send action cancel to reset all the touch events
+                    mIgnoreThisGesture = true;
+                    MotionEvent cancelEv = MotionEvent.obtain(ev);
+                    cancelEv.setAction(MotionEvent.ACTION_CANCEL);
+                    mEdgePanel.handleTouch(cancelEv);
+                    cancelEv.recycle();
+                    return;
+
+                } else if (dx > dy && dx > mTouchSlop) {
+                    mThresholdCrossed = true;
+                    // Capture inputs
+                    mInputMonitor.pilferPointers();
+                }
+            }
+
+            // forward touch
+            mEdgePanel.handleTouch(ev);
+
+            if (ev.getAction() == MotionEvent.ACTION_UP) {
+                float xDiff = ev.getX() - mDownPoint.x;
+                boolean exceedsThreshold = mIsOnLeftEdge
+                        ? (xDiff > mSwipeThreshold) : (-xDiff > mSwipeThreshold);
+                boolean performAction = exceedsThreshold
+                        && Math.abs(xDiff) > Math.abs(ev.getY() - mDownPoint.y);
+                if (performAction) {
+                    // Perform back
+                    sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
+                    sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
+                }
+                mOverviewProxyService.notifyBackAction(performAction, (int) mDownPoint.x,
+                        (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
+            }
+        }
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) { }
+
+    @Override
+    public void onDisplayRemoved(int displayId) { }
+
+    @Override
+    public void onDisplayChanged(int displayId) {
+        if (displayId == mDisplayId) {
+            updateDisplaySize();
+        }
+    }
+
+    private void updateDisplaySize() {
+        mContext.getSystemService(DisplayManager.class)
+                .getDisplay(mDisplayId).getRealSize(mDisplaySize);
+    }
+
+    private void sendEvent(int action, int code) {
+        long when = SystemClock.uptimeMillis();
+        final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
+                0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+                KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+                InputDevice.SOURCE_KEYBOARD);
+        InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 5afff81..1d2ca9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -334,6 +334,11 @@
                 && mExpansion == EXPANSION_VISIBLE && !isAnimatingAway();
     }
 
+    public boolean isPartiallyVisible() {
+        return (mShowingSoon || (mRoot != null && mRoot.getVisibility() == View.VISIBLE))
+                && mExpansion != EXPANSION_HIDDEN && !isAnimatingAway();
+    }
+
     /**
      * @return {@code true} when bouncer's pre-hide animation already started but isn't completely
      *         hidden yet, {@code false} otherwise.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 2bd8d41..6ee031a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -187,9 +187,9 @@
         }
         if (mKeyguardUserSwitcher == null) {
             // If we have no keyguard switcher, the screen width is under 600dp. In this case,
-            // we don't show the multi-user avatar unless there is more than 1 user on the device.
-            if (mUserSwitcherController != null
-                    && mUserSwitcherController.getSwitchableUserCount() > 1) {
+            // we only show the multi-user switch if it's enabled through UserManager as well as
+            // by the user.
+            if (mMultiUserSwitch.isMultiUserEnabled()) {
                 mMultiUserSwitch.setVisibility(View.VISIBLE);
             } else {
                 mMultiUserSwitch.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 27394d9..6ebd6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -263,8 +263,7 @@
             return STATE_BIOMETRICS_ERROR;
         } else if (mUnlockMethodCache.canSkipBouncer()) {
             return STATE_LOCK_OPEN;
-        } else if (mUnlockMethodCache.isFaceUnlockRunning()
-                || updateMonitor.isFaceDetectionRunning()) {
+        } else if (updateMonitor.isFaceDetectionRunning()) {
             return STATE_SCANNING_FACE;
         } else {
             return STATE_LOCKED;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index f393dcd..1d87a8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -16,10 +16,10 @@
 
 package com.android.systemui.statusbar.phone;
 
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
-import android.content.Intent;
 import android.os.UserManager;
-import android.provider.ContactsContract;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.View;
@@ -33,7 +33,6 @@
 import com.android.systemui.Prefs;
 import com.android.systemui.Prefs.Key;
 import com.android.systemui.R;
-import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.qs.QSPanel;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
@@ -95,6 +94,26 @@
         registerListener();
     }
 
+    public boolean isMultiUserEnabled() {
+        // Short-circuiting from UserManager. Needs to be extracted because of SystemUI boolean flag
+        // qs_show_user_switcher_for_single_user
+
+        final boolean userSwitcherEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.USER_SWITCHER_ENABLED, 1) != 0;
+
+        if (!UserManager.supportsMultipleUsers()
+                || mUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)
+                || UserManager.isDeviceInDemoMode(mContext)
+                || !userSwitcherEnabled) {
+            return false;
+        }
+
+        final boolean guestEnabled = !mContext.getSystemService(DevicePolicyManager.class)
+                .getGuestUserDisabled(null);
+        return mUserSwitcherController.getSwitchableUserCount() > 1 || guestEnabled
+                || mContext.getResources().getBoolean(R.bool.qs_show_user_switcher_for_single_user);
+    }
+
     private void registerListener() {
         if (mUserManager.isUserSwitcherEnabled() && mUserListener == null) {
 
@@ -118,29 +137,20 @@
 
     @Override
     public void onClick(View v) {
-        if (mUserManager.isUserSwitcherEnabled()) {
-            if (mKeyguardMode) {
-                if (mKeyguardUserSwitcher != null) {
-                    mKeyguardUserSwitcher.show(true /* animate */);
-                }
-            } else if (mQsPanel != null && mUserSwitcherController != null) {
-                View center = getChildCount() > 0 ? getChildAt(0) : this;
-
-                center.getLocationInWindow(mTmpInt2);
-                mTmpInt2[0] += center.getWidth() / 2;
-                mTmpInt2[1] += center.getHeight() / 2;
-
-                mQsPanel.showDetailAdapter(true,
-                        getUserDetailAdapter(),
-                        mTmpInt2);
+        if (mKeyguardMode) {
+            if (mKeyguardUserSwitcher != null) {
+                mKeyguardUserSwitcher.show(true /* animate */);
             }
-        } else {
-            if (mQsPanel != null) {
-                Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
-                        getContext(), v, ContactsContract.Profile.CONTENT_URI,
-                        ContactsContract.QuickContact.MODE_LARGE, null);
-                Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
-            }
+        } else if (mQsPanel != null && mUserSwitcherController != null) {
+            View center = getChildCount() > 0 ? getChildAt(0) : this;
+
+            center.getLocationInWindow(mTmpInt2);
+            mTmpInt2[0] += center.getWidth() / 2;
+            mTmpInt2[1] += center.getHeight() / 2;
+
+            mQsPanel.showDetailAdapter(true,
+                    getUserDetailAdapter(),
+                    mTmpInt2);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java
deleted file mode 100644
index 323e776..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationAssistantAction.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.annotation.NonNull;
-import android.os.Bundle;
-import android.view.MotionEvent;
-
-import com.android.systemui.assist.AssistManager;
-import com.android.systemui.recents.OverviewProxyService;
-
-/**
- * Assistant is triggered with this action
- */
-public class NavigationAssistantAction extends NavigationGestureAction {
-    private static final String TAG = "NavigationAssistantActions";
-
-    private final AssistManager mAssistManager;
-
-    public NavigationAssistantAction(@NonNull NavigationBarView navigationBarView,
-            @NonNull OverviewProxyService service, AssistManager assistManager) {
-        super(navigationBarView, service);
-        mAssistManager = assistManager;
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return true;
-    }
-
-    @Override
-    public boolean disableProxyEvents() {
-        return true;
-    }
-
-    @Override
-    public void onGestureStart(MotionEvent event) {
-        mAssistManager.startAssist(new Bundle());
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java
deleted file mode 100644
index c77b16b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBackAction.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.annotation.NonNull;
-import android.hardware.input.InputManager;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.view.InputDevice;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import com.android.systemui.recents.OverviewProxyService;
-
-/**
- * A back action when triggered will execute a back command
- */
-public class NavigationBackAction extends NavigationGestureAction {
-
-    private static final String BACK_AFTER_END_PROP =
-            "quickstepcontroller_homegoesbackwhenend";
-    private static final long BACK_BUTTON_FADE_OUT_ALPHA = 60;
-    private static final long BACK_GESTURE_POLL_TIMEOUT = 1000;
-
-    private final Handler mHandler = new Handler();
-
-    private final Runnable mExecuteBackRunnable = new Runnable() {
-        @Override
-        public void run() {
-            if (isEnabled() && canPerformAction()) {
-                performBack();
-                mHandler.postDelayed(this, BACK_GESTURE_POLL_TIMEOUT);
-            }
-        }
-    };
-
-    public NavigationBackAction(@NonNull NavigationBarView navigationBarView,
-            @NonNull OverviewProxyService service) {
-        super(navigationBarView, service);
-    }
-
-    @Override
-    public boolean allowHitTargetToMoveOverDrag() {
-        return true;
-    }
-
-    @Override
-    public boolean canPerformAction() {
-        return mProxySender.getBackButtonAlpha() > 0;
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return true;
-    }
-
-    @Override
-    protected void onGestureStart(MotionEvent event) {
-        if (!QuickStepController.shouldHideBackButton(getContext())) {
-            mNavigationBarView.getBackButton().setAlpha(0 /* alpha */, true /* animate */,
-                    BACK_BUTTON_FADE_OUT_ALPHA);
-        }
-        mHandler.removeCallbacks(mExecuteBackRunnable);
-        if (!shouldExecuteBackOnUp()) {
-            performBack();
-            mHandler.postDelayed(mExecuteBackRunnable, BACK_GESTURE_POLL_TIMEOUT);
-        }
-    }
-
-    @Override
-    protected void onGestureEnd() {
-        mHandler.removeCallbacks(mExecuteBackRunnable);
-        if (!QuickStepController.shouldHideBackButton(getContext())) {
-            mNavigationBarView.getBackButton().setAlpha(
-                    mProxySender.getBackButtonAlpha(), true /* animate */);
-        }
-        if (shouldExecuteBackOnUp()) {
-            performBack();
-        }
-    }
-
-    @Override
-    public boolean disableProxyEvents() {
-        return true;
-    }
-
-    private void performBack() {
-        sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
-        sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
-    }
-
-    private boolean shouldExecuteBackOnUp() {
-        return getGlobalBoolean(BACK_AFTER_END_PROP);
-    }
-
-    private void sendEvent(int action, int code) {
-        long when = SystemClock.uptimeMillis();
-        final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
-                0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
-                KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
-                InputDevice.SOURCE_KEYBOARD);
-        InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
index 4897464..86b5344 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarEdgePanel.java
@@ -17,25 +17,22 @@
 package com.android.systemui.statusbar.phone;
 
 import android.animation.ObjectAnimator;
-import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Paint;
-import android.graphics.PixelFormat;
 import android.os.SystemClock;
+import android.os.VibrationEffect;
 import android.util.FloatProperty;
 import android.util.MathUtils;
-import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.WindowManager;
 
+import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.VibratorHelper;
 
 public class NavigationBarEdgePanel extends View {
-    private static final String TAG = "NavigationBarEdgePanel";
 
     // TODO: read from resources once drawing is finalized.
     private static final boolean SHOW_PROTECTION_STROKE = true;
@@ -52,6 +49,8 @@
     private static final int ANIM_DURATION_MS = 150;
     private static final long HAPTIC_TIMEOUT_MS = 200;
 
+    private final VibratorHelper mVibratorHelper;
+
     private final Paint mPaint = new Paint();
     private final Paint mProtectionPaint = new Paint();
 
@@ -63,9 +62,11 @@
     private final float mPointExtent;
     private final float mHeight;
     private final float mStrokeThickness;
-    private final boolean mIsLeftPanel;
 
-    private float mStartY;
+    private final float mSwipeThreshold;
+
+    private boolean mIsLeftPanel;
+
     private float mStartX;
 
     private boolean mDragSlopPassed;
@@ -105,28 +106,11 @@
                 }
             };
 
-    public static NavigationBarEdgePanel create(@NonNull Context context, int width, int height,
-            int gravity) {
-        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
-                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
-                WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
-                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-                    | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-                    | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
-                PixelFormat.TRANSLUCENT);
-        lp.gravity = gravity;
-        lp.setTitle(TAG + context.getDisplayId());
-        lp.accessibilityTitle = context.getString(R.string.nav_bar_edge_panel);
-        lp.windowAnimations = 0;
-        NavigationBarEdgePanel panel = new NavigationBarEdgePanel(
-                context, (gravity & Gravity.LEFT) == Gravity.LEFT);
-        panel.setLayoutParams(lp);
-        return panel;
-    }
-
-    private NavigationBarEdgePanel(Context context, boolean isLeftPanel) {
+    public NavigationBarEdgePanel(Context context) {
         super(context);
 
+        mVibratorHelper = Dependency.get(VibratorHelper.class);
+
         mEndAnimator = ObjectAnimator.ofFloat(this, DRAG_PROGRESS, 1f);
         mEndAnimator.setAutoCancel(true);
         mEndAnimator.setDuration(ANIM_DURATION_MS);
@@ -154,45 +138,16 @@
 
         // Both panels arrow point the same way
         mArrowsPointLeft = getLayoutDirection() == LAYOUT_DIRECTION_LTR;
+
+        mSwipeThreshold = context.getResources()
+                .getDimension(R.dimen.navigation_edge_action_drag_threshold);
+        setVisibility(GONE);
+    }
+
+    public void setIsLeftPanel(boolean isLeftPanel) {
         mIsLeftPanel = isLeftPanel;
     }
 
-    public void setWindowFlag(int flags, boolean enable) {
-        WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
-        if (lp == null || enable == ((lp.flags & flags) != 0)) {
-            return;
-        }
-        if (enable) {
-            lp.flags |= flags;
-        } else {
-            lp.flags &= ~flags;
-        }
-        updateLayout(lp);
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN : {
-                mDragSlopPassed = false;
-                show(event.getX(), event.getY());
-                break;
-            }
-            case MotionEvent.ACTION_MOVE: {
-                handleNewSwipePoint(event.getX());
-                break;
-            }
-            // Fall through
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL: {
-                hide();
-                break;
-            }
-        }
-
-        return false;
-    }
-
     @Override
     protected void onDraw(Canvas canvas) {
         float edgeOffset = mBaseExtent * mDragProgress - mStrokeThickness;
@@ -200,7 +155,7 @@
         canvas.save();
         canvas.translate(
                 mIsLeftPanel ? edgeOffset : getWidth() - edgeOffset,
-                mStartY - mHeight * 0.5f);
+                (getHeight() - mHeight) * 0.5f);
 
         float outsideX = mArrowsPointLeft ? animatedOffset : 0;
         float middleX = mArrowsPointLeft ? 0 : animatedOffset;
@@ -223,15 +178,6 @@
         mGestureLength = getWidth();
     }
 
-    public void setDimensions(int width, int height) {
-        final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
-        if (lp.width != width || lp.height != height) {
-            lp.width = width;
-            lp.height = height;
-            updateLayout(lp);
-        }
-    }
-
     private void setLegProgress(float progress) {
         mLegProgress = progress;
         invalidate();
@@ -251,29 +197,48 @@
     }
 
     private void hide() {
-        animate().alpha(0f).setDuration(ANIM_DURATION_MS);
+        animate().alpha(0f).setDuration(ANIM_DURATION_MS)
+                .withEndAction(() -> setVisibility(GONE));
     }
 
-    private void show(float x, float y) {
-        mEndAnimator.cancel();
-        mLegAnimator.cancel();
-        setLegProgress(0f);
-        setDragProgress(0f);
-        setAlpha(1f);
-
-        float halfHeight = mHeight * 0.5f;
-        mStartY = MathUtils.constrain(y, halfHeight, getHeight() - halfHeight);
-        mStartX = x;
+    /**
+     * Updates the UI based on the motion events passed in device co-ordinates
+     */
+    public void handleTouch(MotionEvent event) {
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN : {
+                mDragSlopPassed = false;
+                mEndAnimator.cancel();
+                mLegAnimator.cancel();
+                animate().cancel();
+                setLegProgress(0f);
+                setDragProgress(0f);
+                mStartX = event.getX();
+                setVisibility(VISIBLE);
+                break;
+            }
+            case MotionEvent.ACTION_MOVE: {
+                handleNewSwipePoint(event.getX());
+                break;
+            }
+            // Fall through
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL: {
+                hide();
+                break;
+            }
+        }
     }
 
     private void handleNewSwipePoint(float x) {
         float dist = MathUtils.abs(x - mStartX);
 
         // Apply a haptic on drag slop passed
-        if (!mDragSlopPassed && dist > QuickStepContract.getQuickStepDragSlopPx()) {
+        if (!mDragSlopPassed && dist > mSwipeThreshold) {
             mDragSlopPassed = true;
-            performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
+            mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
             mLastSlopHapticTime = SystemClock.uptimeMillis();
+            setAlpha(1f);
         }
 
         setDragProgress(MathUtils.constrainedMap(
@@ -311,11 +276,6 @@
         }
     }
 
-    private void updateLayout(WindowManager.LayoutParams lp) {
-        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
-        wm.updateViewLayout(this, lp);
-    }
-
     private float dp(float dp) {
         return mDensity * dp;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 9485623..3dcadf1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -91,6 +91,7 @@
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.CommandQueue.Callbacks;
@@ -201,7 +202,7 @@
         @Override
         public void onBackButtonAlphaChanged(float alpha, boolean animate) {
             final ButtonDispatcher backButton = mNavigationBarView.getBackButton();
-            if (QuickStepController.shouldHideBackButton(getContext())) {
+            if (QuickStepContract.isGesturalMode(getContext())) {
                 // If property was changed to hide/show back button, going home will trigger
                 // launcher to to change the back button alpha to reflect property change
                 backButton.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index f22ecf6..7abdbd0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -17,19 +17,9 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.content.Intent.ACTION_OVERLAY_CHANGED;
-import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
 
-import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
 import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_DEAD_ZONE;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION;
 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
 import static com.android.systemui.statusbar.phone.NavigationBarInflaterView.NAV_BAR_VIEWS;
 
@@ -40,14 +30,11 @@
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.annotation.DrawableRes;
-import android.annotation.IntDef;
-import android.annotation.SuppressLint;
 import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ParceledListSlice;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Point;
@@ -56,15 +43,11 @@
 import android.graphics.Region.Op;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.os.SystemProperties;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
-import android.view.Gravity;
-import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
 import android.view.MotionEvent;
 import android.view.Surface;
 import android.view.View;
@@ -92,31 +75,19 @@
 import com.android.systemui.recents.RecentsOnboarding;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.NavigationBarCompat;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.WindowManagerWrapper;
-import com.android.systemui.statusbar.phone.NavigationPrototypeController.GestureAction;
-import com.android.systemui.statusbar.phone.NavigationPrototypeController.OnPrototypeChangedListener;
 import com.android.systemui.statusbar.policy.DeadZone;
 import com.android.systemui.statusbar.policy.KeyButtonDrawable;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.function.Consumer;
 
 public class NavigationBarView extends FrameLayout implements PluginListener<NavGesture> {
     final static boolean DEBUG = false;
     final static String TAG = "StatusBar/NavBarView";
 
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({WINDOW_TARGET_BOTTOM, WINDOW_TARGET_LEFT, WINDOW_TARGET_RIGHT})
-    public @interface WindowTarget{}
-    public static final int WINDOW_TARGET_BOTTOM = 0;
-    public static final int WINDOW_TARGET_LEFT = 1;
-    public static final int WINDOW_TARGET_RIGHT = 2;
-
     // slippery nav bar when everything is disabled, e.g. during setup
     final static boolean SLIPPERY_WHEN_DISABLED = true;
 
@@ -134,8 +105,6 @@
     int mDisabledFlags = 0;
     int mNavigationIconHints = 0;
 
-    private @NavigationBarCompat.HitTarget int mDownHitTarget = HIT_TARGET_NONE;
-    private @WindowTarget int mWindowHitTarget = WINDOW_TARGET_BOTTOM;
     private Rect mHomeButtonBounds = new Rect();
     private Rect mBackButtonBounds = new Rect();
     private Rect mRecentsButtonBounds = new Rect();
@@ -148,6 +117,7 @@
     private KeyButtonDrawable mRecentIcon;
     private KeyButtonDrawable mDockedIcon;
 
+    private final EdgeBackGestureHandler mEdgeBackGestureHandler;
     private GestureHelper mGestureHelper;
     private final DeadZone mDeadZone;
     private boolean mDeadZoneConsuming = false;
@@ -175,16 +145,6 @@
 
     private NavBarTintController mTintController;
     private boolean mAssistantAvailable;
-    private NavigationPrototypeController mPrototypeController;
-    private NavigationGestureAction[] mDefaultGestureMap;
-    private QuickScrubAction mQuickScrubAction;
-    private QuickStepAction mQuickStepAction;
-    private NavigationBackAction mBackAction;
-    private QuickSwitchAction mQuickSwitchAction;
-    private NavigationAssistantAction mAssistantAction;
-
-    private NavigationBarEdgePanel mLeftEdgePanel;
-    private NavigationBarEdgePanel mRightEdgePanel;
 
     /**
      * Helper that is responsible for showing the right toast when a disallowed activity operation
@@ -248,18 +208,6 @@
         }
     };
 
-    private final OnTouchListener mEdgePanelTouchListener = new OnTouchListener() {
-        @SuppressLint("ClickableViewAccessibility")
-        @Override
-        public boolean onTouch(View v, MotionEvent event) {
-            if (event.getActionMasked() == ACTION_DOWN) {
-                mWindowHitTarget = v == mLeftEdgePanel ? WINDOW_TARGET_LEFT : WINDOW_TARGET_RIGHT;
-                mDownHitTarget = HIT_TARGET_NONE;
-            }
-            return mGestureHelper.onTouchEvent(event);
-        }
-    };
-
     private final AccessibilityDelegate mQuickStepAccessibilityDelegate
             = new AccessibilityDelegate() {
         private AccessibilityAction mToggleOverviewAction;
@@ -286,104 +234,10 @@
         }
     };
 
-    // TODO(b/112934365): To be removed
-    private OnPrototypeChangedListener mPrototypeListener = new OnPrototypeChangedListener() {
-        @Override
-        public void onGestureRemap(int[] actions) {
-            updateNavigationGestures();
-        }
-
-        @Override
-        public void onBackButtonVisibilityChanged(boolean visible) {
-            if (!inScreenPinning()) {
-                getBackButton().setVisibility(QuickStepController.shouldHideBackButton(getContext())
-                        ? GONE : VISIBLE);
-            }
-        }
-
-        @Override
-        public void onHomeButtonVisibilityChanged(boolean visible) {
-            getHomeButton().setVisibility(QuickStepController.shouldHideHomeButton(getContext())
-                    ? GONE : VISIBLE);
-        }
-
-        @Override
-        public void onColorAdaptChanged(boolean enabled) {
-            if (enabled) {
-                mTintController.start();
-            } else {
-                mTintController.stop();
-            }
-        }
-
-        @Override
-        public void onEdgeSensitivityChanged(int width, int height) {
-            if (mLeftEdgePanel != null) {
-                mLeftEdgePanel.setDimensions(width, height);
-            }
-            if (mRightEdgePanel != null) {
-                mRightEdgePanel.setDimensions(width, height);
-            }
-        }
-
-        @Override
-        public void onHomeHandleVisiblilityChanged(boolean visible) {
-            showHomeHandle(QuickStepController.showHomeHandle(getContext()));
-        }
-
-        @Override
-        public void onAssistantGestureEnabled(boolean enabled) {
-            updateAssistantAvailability();
-        }
-    };
-
-    private final IPinnedStackListener.Stub mImeChangedListener = new IPinnedStackListener.Stub() {
-        @Override
-        public void onListenerRegistered(IPinnedStackController controller) {
-        }
-
-        @Override
-        public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
-            post(() -> {
-                // When the ime changes visibility, resize the edge panels to not cover the ime
-                final int width = mPrototypeController.getEdgeSensitivityWidth();
-                int height = mContext.getDisplay().getHeight() - imeHeight;
-                if (!imeVisible) {
-                    // Hide the navigation bar area at the bottom for gestures
-                    height -= getResources().getDimensionPixelOffset(R.dimen.navigation_bar_height);
-                }
-                if (mLeftEdgePanel != null) {
-                    mLeftEdgePanel.setDimensions(width, height);
-                }
-                if (mRightEdgePanel != null) {
-                    mRightEdgePanel.setDimensions(width, height);
-                }
-            });
-        }
-
-        @Override
-        public void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
-        }
-
-        @Override
-        public void onMinimizedStateChanged(boolean isMinimized) {
-        }
-
-        @Override
-        public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds,
-                Rect animatingBounds, boolean fromImeAdjustment, boolean fromShelfAdjustment,
-                int displayRotation) {
-        }
-
-        @Override
-        public void onActionsChanged(ParceledListSlice actions) {
-        }
-    };
-
     private BroadcastReceiver mOverlaysChangedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            showHomeHandle(QuickStepController.showHomeHandle(getContext()));
+            onOverlaysChanged();
         }
     };
 
@@ -431,23 +285,9 @@
         mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
         mDeadZone = new DeadZone(this);
 
-        mQuickScrubAction = new QuickScrubAction(this, mOverviewProxyService);
-        mQuickStepAction = new QuickStepAction(this, mOverviewProxyService);
-        mBackAction = new NavigationBackAction(this, mOverviewProxyService);
-        mQuickSwitchAction = new QuickSwitchAction(this, mOverviewProxyService);
-        mDefaultGestureMap = new NavigationGestureAction[] {
-                mQuickStepAction, null /* swipeDownAction*/, null /* swipeLeftAction */,
-                mQuickScrubAction, null /* swipeLeftEdgeAction */, null /* swipeRightEdgeAction */
-        };
-
-        mPrototypeController = new NavigationPrototypeController(context);
-        mPrototypeController.register();
-        mPrototypeController.setOnPrototypeChangedListener(mPrototypeListener);
+        mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService);
         mTintController = new NavBarTintController(this, getLightTransitionsController());
 
-        IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
-        filter.addDataScheme("package");
-        context.registerReceiver(mOverlaysChangedReceiver, filter);
     }
 
     public NavBarTintController getTintController() {
@@ -464,14 +304,6 @@
 
     public void setComponents(NotificationPanelView panel, AssistManager assistManager) {
         mPanelView = panel;
-        if (mAssistantAction == null) {
-            mAssistantAction = new NavigationAssistantAction(this, mOverviewProxyService,
-                    assistManager);
-        }
-        if (mGestureHelper instanceof QuickStepController) {
-            ((QuickStepController) mGestureHelper).setComponents(this);
-            updateNavigationGestures();
-        }
     }
 
     @Override
@@ -480,44 +312,6 @@
         mTintController.onDraw();
     }
 
-    private void updateNavigationGestures() {
-        if (mGestureHelper instanceof QuickStepController) {
-            // TODO: Clarify this when we remove the prototype controller
-            final int[] gesturalMap = {0, 7, 1, 1, 3, 3};
-            final int[] normalMap = {0, 0, 0, 0, 0, 0};
-            final int[] assignedMap = QuickStepContract.isGesturalMode(getContext())
-                    ? gesturalMap
-                    : normalMap;
-            ((QuickStepController) mGestureHelper).setGestureActions(
-                    getNavigationActionFromType(assignedMap[0], mDefaultGestureMap[0]),
-                    getNavigationActionFromType(assignedMap[1], mDefaultGestureMap[1]),
-                    getNavigationActionFromType(assignedMap[2], mDefaultGestureMap[2]),
-                    getNavigationActionFromType(assignedMap[3], mDefaultGestureMap[3]),
-                    getNavigationActionFromType(assignedMap[4], mDefaultGestureMap[4]),
-                    getNavigationActionFromType(assignedMap[5], mDefaultGestureMap[5]));
-        }
-    }
-
-    private NavigationGestureAction getNavigationActionFromType(@GestureAction int actionType,
-            NavigationGestureAction defaultAction) {
-        switch(actionType) {
-            case NavigationPrototypeController.ACTION_QUICKSTEP:
-                return mQuickStepAction;
-            case NavigationPrototypeController.ACTION_QUICKSCRUB:
-                return mQuickScrubAction;
-            case NavigationPrototypeController.ACTION_BACK:
-                return mBackAction;
-            case NavigationPrototypeController.ACTION_QUICKSWITCH:
-                return mQuickSwitchAction;
-            case NavigationPrototypeController.ACTION_ASSISTANT:
-                return mAssistantAction;
-            case NavigationPrototypeController.ACTION_NOTHING:
-                return null;
-            default:
-                return defaultAction;
-        }
-    }
-
     public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
         mOnVerticalChangedListener = onVerticalChangedListener;
         notifyVerticalChangedListener(mIsVertical);
@@ -525,28 +319,7 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
-        final boolean deadZoneConsumed = shouldDeadZoneConsumeTouchEvents(event);
-        switch (event.getActionMasked()) {
-            case ACTION_DOWN:
-                int x = (int) event.getX();
-                int y = (int) event.getY();
-                mDownHitTarget = HIT_TARGET_NONE;
-                mWindowHitTarget = WINDOW_TARGET_BOTTOM;
-                if (deadZoneConsumed) {
-                    mDownHitTarget = HIT_TARGET_DEAD_ZONE;
-                } else if (getBackButton().isVisible() && mBackButtonBounds.contains(x, y)) {
-                    mDownHitTarget = HIT_TARGET_BACK;
-                } else if (getHomeButton().isVisible() && mHomeButtonBounds.contains(x, y)) {
-                    mDownHitTarget = HIT_TARGET_HOME;
-                } else if (getRecentsButton().isVisible() && mRecentsButtonBounds.contains(x, y)) {
-                    mDownHitTarget = HIT_TARGET_OVERVIEW;
-                } else if (getRotateSuggestionButton().isVisible()
-                        && mRotationButtonBounds.contains(x, y)) {
-                    mDownHitTarget = HIT_TARGET_ROTATION;
-                }
-                break;
-        }
-        return mGestureHelper.onInterceptTouchEvent(event);
+        return shouldDeadZoneConsumeTouchEvents(event) || super.onInterceptTouchEvent(event);
     }
 
     @Override
@@ -570,8 +343,12 @@
     }
 
     private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) {
+        int action = event.getActionMasked();
+        if (action == MotionEvent.ACTION_DOWN) {
+            mDeadZoneConsuming = false;
+        }
         if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) {
-            switch (event.getActionMasked()) {
+            switch (action) {
                 case MotionEvent.ACTION_DOWN:
                     // Allow gestures starting in the deadzone to be slippery
                     setSlippery(true);
@@ -589,14 +366,6 @@
         return false;
     }
 
-    public @NavigationBarCompat.HitTarget int getDownHitTarget() {
-        return mDownHitTarget;
-    }
-
-    public @WindowTarget int getWindowTarget() {
-        return mWindowHitTarget;
-    }
-
     public void abortCurrentGesture() {
         getHomeButton().abortCurrentGesture();
     }
@@ -654,13 +423,6 @@
         return mOverviewProxyService.shouldShowSwipeUpUI() && isOverviewEnabled();
     }
 
-    public boolean isQuickScrubEnabled() {
-        // TODO(b/112934365): Remove this sys prop flag
-        return SystemProperties.getBoolean("persist.quickstep.scrub.enabled", true)
-                && mOverviewProxyService.isEnabled() && isOverviewEnabled()
-                && ((mOverviewProxyService.getInteractionFlags() & FLAG_DISABLE_QUICK_SCRUB) == 0);
-    }
-
     private void reloadNavIcons() {
         updateIcons(Configuration.EMPTY);
     }
@@ -802,7 +564,7 @@
 
         mBarTransitions.reapplyDarkIntensity();
 
-        boolean disableHome = QuickStepController.shouldHideHomeButton(getContext())
+        boolean disableHome = QuickStepContract.isGesturalMode(getContext())
                 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
 
         // TODO(b/113914868): investigation log for disappearing home button
@@ -812,7 +574,7 @@
         // Always disable recents when alternate car mode UI is active and for secondary displays.
         boolean disableRecent = isRecentsButtonDisabled();
 
-        boolean disableBack = QuickStepController.shouldHideBackButton(getContext())
+        boolean disableBack = QuickStepContract.isGesturalMode(getContext())
                 || (((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) && !useAltBack);
 
         // When screen pinning, don't hide back and home when connected service or back and
@@ -921,10 +683,6 @@
         final boolean showSwipeUpUI = mOverviewProxyService.shouldShowSwipeUpUI();
 
         if (mNavigationInflaterView != null) {
-            if (mPrototypeController.showHomeHandle()) {
-                showHomeHandle(true /* visible */);
-            }
-
             // Reinflate the navbar if needed, no-op unless the swipe up state changes
             mNavigationInflaterView.onLikelyDefaultLayoutChange();
         }
@@ -957,16 +715,6 @@
         setWindowFlag(WindowManager.LayoutParams.FLAG_SLIPPERY, slippery);
     }
 
-    public void setWindowTouchable(boolean flag) {
-        setWindowFlag(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, !flag);
-        if (mLeftEdgePanel != null) {
-            mLeftEdgePanel.setWindowFlag(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, !flag);
-        }
-        if (mRightEdgePanel != null) {
-            mRightEdgePanel.setWindowFlag(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, !flag);
-        }
-    }
-
     private void setWindowFlag(int flags, boolean enable) {
         final ViewGroup navbarView = ((ViewGroup) getParent());
         if (navbarView == null) {
@@ -985,15 +733,16 @@
         wm.updateViewLayout(navbarView, lp);
     }
 
-    private void showHomeHandle(boolean visible) {
+    private void onOverlaysChanged() {
         mNavigationInflaterView.onTuningChanged(NAV_BAR_VIEWS, null);
 
         // Color adaption is tied with showing home handle, only avaliable if visible
-        if (visible) {
+        if (QuickStepContract.isGesturalMode(getContext())) {
             mTintController.start();
         } else {
             mTintController.stop();
         }
+        mEdgeBackGestureHandler.onOverlaysChanged();
     }
 
     public void setAssistantAvailable(boolean available) {
@@ -1186,17 +935,6 @@
         } catch (RemoteException e) {
             Slog.e(TAG, "Failed to get nav bar position.", e);
         }
-
-        // For landscape, hide the panel that would interfere with navigation bar layout
-        if (mLeftEdgePanel != null && mRightEdgePanel != null) {
-            mLeftEdgePanel.setVisibility(VISIBLE);
-            mRightEdgePanel.setVisibility(VISIBLE);
-            if (navBarPos == NAV_BAR_LEFT) {
-                mLeftEdgePanel.setVisibility(GONE);
-            } else if (navBarPos == NAV_BAR_RIGHT) {
-                mRightEdgePanel.setVisibility(GONE);
-            }
-        }
         mGestureHelper.setBarState(isRtl, navBarPos);
     }
 
@@ -1320,27 +1058,10 @@
                 NavGesture.class, false /* Only one */);
         setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
 
-        if (QuickStepContract.isGesturalMode(getContext())) {
-            WindowManager wm = (WindowManager) getContext()
-                    .getSystemService(Context.WINDOW_SERVICE);
-            int width = mPrototypeController.getEdgeSensitivityWidth();
-            int height = mPrototypeController.getEdgeSensitivityHeight();
-            // Explicitly left and right, not start and end as this is device relative.
-            mLeftEdgePanel = NavigationBarEdgePanel.create(getContext(), width, height,
-                    Gravity.LEFT | Gravity.TOP);
-            mRightEdgePanel = NavigationBarEdgePanel.create(getContext(), width, height,
-                    Gravity.RIGHT | Gravity.TOP);
-            mLeftEdgePanel.setOnTouchListener(mEdgePanelTouchListener);
-            mRightEdgePanel.setOnTouchListener(mEdgePanelTouchListener);
-            wm.addView(mLeftEdgePanel, mLeftEdgePanel.getLayoutParams());
-            wm.addView(mRightEdgePanel, mRightEdgePanel.getLayoutParams());
-
-            try {
-                WindowManagerWrapper.getInstance().addPinnedStackListener(mImeChangedListener);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to register pinned stack listener", e);
-            }
-        }
+        IntentFilter filter = new IntentFilter(ACTION_OVERLAY_CHANGED);
+        filter.addDataScheme("package");
+        getContext().registerReceiver(mOverlaysChangedReceiver, filter);
+        mEdgeBackGestureHandler.onNavBarAttached();
     }
 
     @Override
@@ -1350,21 +1071,13 @@
         if (mGestureHelper != null) {
             mGestureHelper.destroy();
         }
-        mPrototypeController.unregister();
-        getContext().unregisterReceiver(mOverlaysChangedReceiver);
         setUpSwipeUpOnboarding(false);
         for (int i = 0; i < mButtonDispatchers.size(); ++i) {
             mButtonDispatchers.valueAt(i).onDestroy();
         }
 
-        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
-        if (mLeftEdgePanel != null) {
-            wm.removeView(mLeftEdgePanel);
-        }
-        if (mRightEdgePanel != null) {
-            wm.removeView(mRightEdgePanel);
-        }
-        WindowManagerWrapper.getInstance().removePinnedStackListener(mImeChangedListener);
+        getContext().unregisterReceiver(mOverlaysChangedReceiver);
+        mEdgeBackGestureHandler.onNavBarDetached();
     }
 
     private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) {
@@ -1383,12 +1096,10 @@
 
     @Override
     public void onPluginDisconnected(NavGesture plugin) {
-        QuickStepController defaultHelper = new QuickStepController(getContext());
-        defaultHelper.setComponents(this);
         if (mGestureHelper != null) {
             mGestureHelper.destroy();
+            mGestureHelper = null;
         }
-        mGestureHelper = defaultHelper;
         updateTaskSwitchHelper();
     }
 
@@ -1431,14 +1142,6 @@
 
         mContextualButtonGroup.dump(pw);
         if (mGestureHelper != null) {
-            pw.println("Navigation Gesture Actions {");
-            pw.print("    "); pw.println("QuickScrub Enabled=" + mQuickScrubAction.isEnabled());
-            pw.print("    "); pw.println("QuickScrub Active=" + mQuickScrubAction.isActive());
-            pw.print("    "); pw.println("QuickStep Enabled=" + mQuickStepAction.isEnabled());
-            pw.print("    "); pw.println("QuickStep Active=" + mQuickStepAction.isActive());
-            pw.print("    "); pw.println("Back Gesture Enabled=" + mBackAction.isEnabled());
-            pw.print("    "); pw.println("Back Gesture Active=" + mBackAction.isActive());
-            pw.println("}");
             mGestureHelper.dump(pw);
         }
         mRecentsOnboarding.dump(pw);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
deleted file mode 100644
index eca14eb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationGestureAction.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
-
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.view.MotionEvent;
-
-import com.android.systemui.recents.OverviewProxyService;
-
-/**
- * A gesture action that would be triggered and reassigned by {@link QuickStepController}
- */
-public abstract class NavigationGestureAction {
-    protected final NavigationBarView mNavigationBarView;
-    protected final OverviewProxyService mProxySender;
-
-    protected int mNavigationBarPosition;
-    protected boolean mDragHorizontalPositive;
-    protected boolean mDragVerticalPositive;
-    private boolean mIsActive;
-
-    public NavigationGestureAction(@NonNull NavigationBarView navigationBarView,
-            @NonNull OverviewProxyService service) {
-        mNavigationBarView = navigationBarView;
-        mProxySender = service;
-    }
-
-    /**
-     * Pass event that the state of the bar (such as rotation) has changed
-     * @param changed if rotation or drag positive direction (such as ltr) has changed
-     * @param navBarPos position of navigation bar
-     * @param dragHorPositive direction of positive horizontal drag, could change with ltr changes
-     * @param dragVerPositive direction of positive vertical drag, could change with ltr changes
-     */
-    public void setBarState(boolean changed, int navBarPos, boolean dragHorPositive,
-            boolean dragVerPositive) {
-        mNavigationBarPosition = navBarPos;
-        mDragHorizontalPositive = dragHorPositive;
-        mDragVerticalPositive = dragVerPositive;
-    }
-
-    /**
-     * Resets the state of the action. Called when touch down occurs over the Navigation Bar.
-     */
-    public void reset() {
-        mIsActive = false;
-    }
-
-    /**
-     * Start the gesture and the action will be active
-     * @param event the event that caused the gesture
-     */
-    public void startGesture(MotionEvent event) {
-        mIsActive = true;
-        onGestureStart(event);
-    }
-
-    /**
-     * Gesture has ended with action cancel or up and this action will not be active
-     */
-    public void endGesture() {
-        mIsActive = false;
-        onGestureEnd();
-    }
-
-    /**
-     * If the action is currently active based on the gesture that triggered it. Only one action
-     * can occur at a time
-     * @return whether or not if this action has been triggered
-     */
-    public boolean isActive() {
-        return mIsActive;
-    }
-
-    /**
-     * @return whether or not this action can run if notification shade is shown
-     */
-    public boolean canRunWhenNotificationsShowing() {
-        return true;
-    }
-
-    /**
-     * @return whether or not this action triggers when starting a gesture from a certain hit target
-     * If {@link HIT_TARGET_NONE} is specified then action does not need to be triggered by button
-     */
-    public int requiresTouchDownHitTarget() {
-        return HIT_TARGET_NONE;
-    }
-
-    /**
-     * @return whether or not to move the button that started gesture over with user input drag
-     */
-    public boolean allowHitTargetToMoveOverDrag() {
-        return false;
-    }
-
-    /**
-     * Tell if the action is able to execute. Note that {@link #isEnabled()} must be true for this
-     * to be checked. The difference between this and {@link #isEnabled()} is that this dependent
-     * on the state of the navigation bar
-     * @return true if action can execute after gesture activates based on current states
-     */
-    public boolean canPerformAction() {
-        return true;
-    }
-
-    /**
-     * Decide if the controller should not send the current motion event to launcher via
-     * {@link OverviewProxyService}
-     * @return if controller should not proxy
-     */
-    public boolean disableProxyEvents() {
-        return false;
-    }
-
-    /**
-     * Tell if action is enabled. Compared to {@link #canPerformAction()} this is based on settings
-     * if the action is disabled for a particular gesture. For example a back action can be enabled
-     * however if there is nothing to back to then {@link #canPerformAction()} should return false.
-     * In this way if the action requires {@link #allowHitTargetToMoveOverDrag()} then if enabled,
-     * the button can be dragged with a large dampening factor during the gesture but will not
-     * activate the action.
-     * @return true if this action is enabled and can run
-     */
-    public abstract boolean isEnabled();
-
-    protected void onDarkIntensityChange(float intensity) {
-    }
-
-    protected void onDraw(Canvas canvas) {
-    }
-
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-    }
-
-    /**
-     * When gesture starts, this will run to execute the action
-     * @param event the event that triggered the gesture
-     */
-    protected abstract void onGestureStart(MotionEvent event);
-
-    /**
-     * Channels motion move events to the action to track the user inputs
-     * @param x the x position
-     * @param y the y position
-     */
-    public void onGestureMove(int x, int y) {
-    }
-
-    /**
-     * When gesture ends, this will run from action up or cancel
-     */
-    protected void onGestureEnd() {
-    }
-
-    protected Context getContext() {
-        return mNavigationBarView.getContext();
-    }
-
-    protected boolean isNavBarVertical() {
-        return mNavigationBarPosition == NAV_BAR_LEFT || mNavigationBarPosition == NAV_BAR_RIGHT;
-    }
-
-    protected boolean getGlobalBoolean(@NonNull String key) {
-        return QuickStepController.getBoolGlobalSetting(getContext(), key);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 02bad73..cff3855 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -152,9 +152,11 @@
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     @VisibleForTesting
     protected KeyguardStatusBarView mKeyguardStatusBar;
-    private ViewGroup mBigClockContainer;
+    @VisibleForTesting
+    protected ViewGroup mBigClockContainer;
     private QS mQs;
-    private FrameLayout mQsFrame;
+    @VisibleForTesting
+    protected FrameLayout mQsFrame;
     @VisibleForTesting
     protected KeyguardStatusView mKeyguardStatusView;
     private View mQsNavbarScrim;
@@ -266,6 +268,7 @@
     private int mIndicationBottomPadding;
     private int mAmbientIndicationBottomPadding;
     private boolean mIsFullWidth;
+    private boolean mBlockingExpansionForCurrentTouch;
 
     /**
      * Current dark amount that follows regular interpolation curve of animation.
@@ -983,6 +986,11 @@
             return false;
         }
         initDownStates(event);
+        // Make sure the next touch won't the blocked after the current ends.
+        if (event.getAction() == MotionEvent.ACTION_UP
+                || event.getAction() == MotionEvent.ACTION_CANCEL) {
+            mBlockingExpansionForCurrentTouch = false;
+        }
         if (!mIsExpanding && mPulseExpansionHandler.onTouchEvent(event)) {
             // We're expanding all the other ones shouldn't get this anymore
             return true;
@@ -1662,7 +1670,7 @@
         if (!mQsExpansionEnabled || mCollapsedOnDown) {
             return false;
         }
-        View header = mKeyguardShowing ? mKeyguardStatusBar : mQs.getHeader();
+        View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
         final boolean onHeader = x >= mQsFrame.getX()
                 && x <= mQsFrame.getX() + mQsFrame.getWidth()
                 && y >= header.getTop() && y <= header.getBottom();
@@ -2337,7 +2345,7 @@
 
     @Override
     protected boolean isTrackingBlocked() {
-        return mConflictingQsExpansionGesture && mQsExpanded;
+        return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
     }
 
     public boolean isQsExpanded() {
@@ -2937,6 +2945,14 @@
         updateLockIcon();
     }
 
+    /**
+     * Do not let the user drag the shade up and down for the current touch session.
+     * This is necessary to avoid shade expansion while/after the bouncer is dismissed.
+     */
+    public void blockExpansionForCurrentTouch() {
+        mBlockingExpansionForCurrentTouch = mTracking;
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         super.dump(fd, pw, args);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java
deleted file mode 100644
index bbfd51a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubAction.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.Interpolators.ALPHA_IN;
-import static com.android.systemui.Interpolators.ALPHA_OUT;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.annotation.NonNull;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.RadialGradient;
-import android.graphics.Shader;
-import android.util.FloatProperty;
-import android.view.MotionEvent;
-import android.view.View;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.shared.recents.utilities.Utilities;
-
-/**
- * QuickScrub action to send to launcher to start quickscrub gesture
- */
-public class QuickScrubAction extends QuickSwitchAction {
-    private static final String TAG = "QuickScrubAction";
-
-    private static final float TRACK_SCALE = 0.95f;
-    private static final float GRADIENT_WIDTH = .75f;
-    private static final int ANIM_IN_DURATION_MS = 150;
-    private static final int ANIM_OUT_DURATION_MS = 134;
-
-    private AnimatorSet mTrackAnimator;
-    private View mCurrentNavigationBarView;
-
-    private float mTrackScale = TRACK_SCALE;
-    private float mTrackAlpha;
-    private float mHighlightCenter;
-    private float mDarkIntensity;
-
-    private final int mTrackThickness;
-    private final int mTrackEndPadding;
-    private final Paint mTrackPaint = new Paint();
-
-    private final FloatProperty<QuickScrubAction> mTrackAlphaProperty =
-            new FloatProperty<QuickScrubAction>("TrackAlpha") {
-        @Override
-        public void setValue(QuickScrubAction action, float alpha) {
-            mTrackAlpha = alpha;
-            mNavigationBarView.invalidate();
-        }
-
-        @Override
-        public Float get(QuickScrubAction action) {
-            return mTrackAlpha;
-        }
-    };
-
-    private final FloatProperty<QuickScrubAction> mTrackScaleProperty =
-            new FloatProperty<QuickScrubAction>("TrackScale") {
-        @Override
-        public void setValue(QuickScrubAction action, float scale) {
-            mTrackScale = scale;
-            mNavigationBarView.invalidate();
-        }
-
-        @Override
-        public Float get(QuickScrubAction action) {
-            return mTrackScale;
-        }
-    };
-
-    private final FloatProperty<QuickScrubAction> mNavBarAlphaProperty =
-            new FloatProperty<QuickScrubAction>("NavBarAlpha") {
-        @Override
-        public void setValue(QuickScrubAction action, float alpha) {
-            if (mCurrentNavigationBarView != null) {
-                mCurrentNavigationBarView.setAlpha(alpha);
-            }
-        }
-
-        @Override
-        public Float get(QuickScrubAction action) {
-            if (mCurrentNavigationBarView != null) {
-                return mCurrentNavigationBarView.getAlpha();
-            }
-            return 1f;
-        }
-    };
-
-    private AnimatorListenerAdapter mQuickScrubEndListener = new AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            if (mCurrentNavigationBarView != null) {
-                mCurrentNavigationBarView.setAlpha(1f);
-            }
-            mCurrentNavigationBarView = null;
-            updateHighlight();
-        }
-    };
-
-    public QuickScrubAction(@NonNull NavigationBarView navigationBarView,
-            @NonNull OverviewProxyService service) {
-        super(navigationBarView, service);
-        mTrackPaint.setAntiAlias(true);
-        mTrackPaint.setDither(true);
-
-        final Resources res = navigationBarView.getResources();
-        mTrackThickness = res.getDimensionPixelSize(R.dimen.nav_quick_scrub_track_thickness);
-        mTrackEndPadding = res.getDimensionPixelSize(R.dimen.nav_quick_scrub_track_edge_padding);
-    }
-
-    @Override
-    public void setBarState(boolean changed, int navBarPos, boolean dragHorPositive,
-            boolean dragVerPositive) {
-        super.setBarState(changed, navBarPos, dragHorPositive, dragVerPositive);
-        if (changed && isActive()) {
-            // End quickscrub if the state changes mid-transition
-            endQuickScrub(false /* animate */);
-        }
-    }
-
-    @Override
-    public void reset() {
-        super.reset();
-
-        // End any existing quickscrub animations before starting the new transition
-        if (mTrackAnimator != null) {
-            mTrackAnimator.end();
-            mTrackAnimator = null;
-        }
-        mCurrentNavigationBarView = mNavigationBarView.getCurrentView();
-    }
-
-    @Override
-    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        final int paddingLeft = mNavigationBarView.getPaddingLeft();
-        final int paddingTop = mNavigationBarView.getPaddingTop();
-        final int paddingRight = mNavigationBarView.getPaddingRight();
-        final int paddingBottom = mNavigationBarView.getPaddingBottom();
-        final int width = (right - left) - paddingRight - paddingLeft;
-        final int height = (bottom - top) - paddingBottom - paddingTop;
-        final int x1, x2, y1, y2;
-        if (isNavBarVertical()) {
-            x1 = (width - mTrackThickness) / 2 + paddingLeft;
-            x2 = x1 + mTrackThickness;
-            y1 = paddingTop + mTrackEndPadding;
-            y2 = y1 + height - 2 * mTrackEndPadding;
-        } else {
-            y1 = (height - mTrackThickness) / 2 + paddingTop;
-            y2 = y1 + mTrackThickness;
-            x1 = mNavigationBarView.getPaddingStart() + mTrackEndPadding;
-            x2 = x1 + width - 2 * mTrackEndPadding;
-        }
-        mDragOverRect.set(x1, y1, x2, y2);
-    }
-
-    @Override
-    public void onDarkIntensityChange(float intensity) {
-        mDarkIntensity = intensity;
-        updateHighlight();
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        if (!isEnabled()) {
-            return;
-        }
-        mTrackPaint.setAlpha(Math.round(255f * mTrackAlpha));
-
-        // Scale the track, but apply the inverse scale from the nav bar
-        final float radius = mDragOverRect.height() / 2;
-        canvas.save();
-        float translate = Utilities.clamp(mHighlightCenter, mDragOverRect.left,
-                mDragOverRect.right);
-        canvas.translate(translate, 0);
-        canvas.scale(mTrackScale / mNavigationBarView.getScaleX(),
-                1f / mNavigationBarView.getScaleY(),
-                mDragOverRect.centerX(), mDragOverRect.centerY());
-        canvas.drawRoundRect(mDragOverRect.left - translate, mDragOverRect.top,
-                mDragOverRect.right - translate, mDragOverRect.bottom, radius, radius, mTrackPaint);
-        canvas.restore();
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return mNavigationBarView.isQuickScrubEnabled();
-    }
-
-    @Override
-    protected void onGestureStart(MotionEvent event) {
-        updateHighlight();
-        ObjectAnimator trackAnimator = ObjectAnimator.ofPropertyValuesHolder(this,
-                PropertyValuesHolder.ofFloat(mTrackAlphaProperty, 1f),
-                PropertyValuesHolder.ofFloat(mTrackScaleProperty, 1f));
-        trackAnimator.setInterpolator(ALPHA_IN);
-        trackAnimator.setDuration(ANIM_IN_DURATION_MS);
-        ObjectAnimator navBarAnimator = ObjectAnimator.ofFloat(this, mNavBarAlphaProperty, 0f);
-        navBarAnimator.setInterpolator(ALPHA_OUT);
-        navBarAnimator.setDuration(ANIM_OUT_DURATION_MS);
-        mTrackAnimator = new AnimatorSet();
-        mTrackAnimator.playTogether(trackAnimator, navBarAnimator);
-        mTrackAnimator.start();
-
-        startQuickGesture(event);
-    }
-
-    @Override
-    public void onGestureMove(int x, int y) {
-        super.onGestureMove(x, y);
-        mHighlightCenter = x;
-        mNavigationBarView.invalidate();
-    }
-
-    @Override
-    protected void onGestureEnd() {
-        endQuickScrub(true /* animate */);
-    }
-
-    private void endQuickScrub(boolean animate) {
-        animateEnd();
-        endQuickGesture(animate);
-        if (!animate) {
-            if (mTrackAnimator != null) {
-                mTrackAnimator.end();
-                mTrackAnimator = null;
-            }
-        }
-    }
-
-    private void updateHighlight() {
-        if (mDragOverRect.isEmpty()) {
-            return;
-        }
-        int colorBase, colorGrad;
-        if (mDarkIntensity > 0.5f) {
-            colorBase = getContext().getColor(R.color.quick_step_track_background_background_dark);
-            colorGrad = getContext().getColor(R.color.quick_step_track_background_foreground_dark);
-        } else {
-            colorBase = getContext().getColor(R.color.quick_step_track_background_background_light);
-            colorGrad = getContext().getColor(R.color.quick_step_track_background_foreground_light);
-        }
-        final RadialGradient mHighlight = new RadialGradient(0, mDragOverRect.height() / 2,
-                mDragOverRect.width() * GRADIENT_WIDTH, colorGrad, colorBase,
-                Shader.TileMode.CLAMP);
-        mTrackPaint.setShader(mHighlight);
-    }
-
-    private void animateEnd() {
-        if (mTrackAnimator != null) {
-            mTrackAnimator.cancel();
-        }
-
-        ObjectAnimator trackAnimator = ObjectAnimator.ofPropertyValuesHolder(this,
-                PropertyValuesHolder.ofFloat(mTrackAlphaProperty, 0f),
-                PropertyValuesHolder.ofFloat(mTrackScaleProperty, TRACK_SCALE));
-        trackAnimator.setInterpolator(ALPHA_OUT);
-        trackAnimator.setDuration(ANIM_OUT_DURATION_MS);
-        ObjectAnimator navBarAnimator = ObjectAnimator.ofFloat(this, mNavBarAlphaProperty, 1f);
-        navBarAnimator.setInterpolator(ALPHA_IN);
-        navBarAnimator.setDuration(ANIM_IN_DURATION_MS);
-        mTrackAnimator = new AnimatorSet();
-        mTrackAnimator.playTogether(trackAnimator, navBarAnimator);
-        mTrackAnimator.addListener(mQuickScrubEndListener);
-        mTrackAnimator.start();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java
deleted file mode 100644
index b18b79e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepAction.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.recents.OverviewProxyService.DEBUG_OVERVIEW_PROXY;
-import static com.android.systemui.recents.OverviewProxyService.TAG_OPS;
-
-import android.annotation.NonNull;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.systemui.recents.OverviewProxyService;
-
-/**
- * QuickStep action to send to launcher to start overview
- */
-public class QuickStepAction extends NavigationGestureAction {
-    private static final String TAG = "QuickStepAction";
-
-    public QuickStepAction(@NonNull NavigationBarView navigationBarView,
-            @NonNull OverviewProxyService service) {
-        super(navigationBarView, service);
-    }
-
-    @Override
-    public boolean canRunWhenNotificationsShowing() {
-        return false;
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return mNavigationBarView.isQuickStepSwipeUpEnabled();
-    }
-
-    @Override
-    public void onGestureStart(MotionEvent event) {
-        try {
-            mProxySender.getProxy().onQuickStep(event);
-            if (DEBUG_OVERVIEW_PROXY) {
-                Log.d(TAG_OPS, "Quick Step Start");
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to send quick step started.", e);
-        }
-        mProxySender.notifyQuickStepStarted();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
deleted file mode 100644
index 8053ec7..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
+++ /dev/null
@@ -1,731 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
-
-import static com.android.systemui.recents.OverviewProxyService.DEBUG_OVERVIEW_PROXY;
-import static com.android.systemui.recents.OverviewProxyService.TAG_OPS;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_DEAD_ZONE;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
-import static com.android.systemui.statusbar.phone.NavigationBarView.WINDOW_TARGET_BOTTOM;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.hardware.input.InputManager;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewPropertyAnimator;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.SysUiServiceProvider;
-import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.shared.recents.IOverviewProxy;
-import com.android.systemui.shared.recents.utilities.Utilities;
-import com.android.systemui.shared.system.InputChannelCompat.InputEventDispatcher;
-import com.android.systemui.shared.system.NavigationBarCompat;
-import com.android.systemui.shared.system.QuickStepContract;
-
-import java.io.PrintWriter;
-
-/**
- * Class to detect gestures on the navigation bar and implement quick scrub.
- * Note that the variables in this class horizontal and vertical represents horizontal always
- * aligned with along the navigation bar.
- */
-public class QuickStepController implements GestureHelper {
-
-    private static final String TAG = "QuickStepController";
-
-    /** Experiment to swipe home button left to execute a back key press */
-    private static final String HIDE_BACK_BUTTON_PROP = "quickstepcontroller_hideback";
-    private static final String HIDE_HOME_BUTTON_PROP = "quickstepcontroller_hidehome";
-    private static final String ENABLE_CLICK_THROUGH_NAV_PROP = "quickstepcontroller_clickthrough";
-    private static final String GESTURE_REGION_THRESHOLD_SETTING = "gesture_region_threshold";
-    private static final long BACK_BUTTON_FADE_IN_ALPHA = 150;
-    private static final long CLICK_THROUGH_TAP_DELAY = 70;
-    private static final long CLICK_THROUGH_TAP_RESET_DELAY = 100;
-
-    /** When the home-swipe-back gesture is disallowed, make it harder to pull */
-    private static final float HORIZONTAL_GESTURE_DAMPING = 0.3f;
-    private static final float VERTICAL_GESTURE_DAMPING = 0.15f;
-    private static final float HORIZONTAL_DISABLED_GESTURE_DAMPING = 0.16f;
-    private static final float VERTICAL_DISABLED_GESTURE_DAMPING = 0.06f;
-
-    private static final int ACTION_SWIPE_UP_INDEX = 0;
-    private static final int ACTION_SWIPE_DOWN_INDEX = 1;
-    private static final int ACTION_SWIPE_LEFT_INDEX = 2;
-    private static final int ACTION_SWIPE_RIGHT_INDEX = 3;
-    private static final int ACTION_SWIPE_LEFT_FROM_EDGE_INDEX = 4;
-    private static final int ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX = 5;
-    private static final int MAX_GESTURES = 6;
-
-    private NavigationBarView mNavigationBarView;
-
-    private boolean mAllowGestureDetection;
-    private boolean mNotificationsVisibleOnDown;
-    private int mTouchDownX;
-    private int mTouchDownY;
-    private boolean mDragHPositive;
-    private boolean mDragVPositive;
-    private boolean mIsRTL;
-    private int mNavBarPosition;
-    private float mDarkIntensity;
-    private ViewPropertyAnimator mDragBtnAnimator;
-    private ButtonDispatcher mHitTarget;
-    private boolean mIsInScreenPinning;
-    private boolean mGestureHorizontalDragsButton;
-    private boolean mGestureVerticalDragsButton;
-    private float mMaxDragLimit;
-    private float mMinDragLimit;
-    private float mDragDampeningFactor;
-    private boolean mClickThroughPressed;
-    private float mClickThroughPressX;
-    private float mClickThroughPressY;
-    private int mGestureRegionThreshold;
-
-    private NavigationGestureAction mCurrentAction;
-    private NavigationGestureAction[] mGestureActions = new NavigationGestureAction[MAX_GESTURES];
-
-    private final Rect mLastLayoutRect = new Rect();
-    private final OverviewProxyService mOverviewEventSender;
-    private final Context mContext;
-    private final StatusBar mStatusBar;
-    private final Matrix mTransformGlobalMatrix = new Matrix();
-    private final Matrix mTransformLocalMatrix = new Matrix();
-
-    public QuickStepController(Context context) {
-        mContext = context;
-        mStatusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
-        mOverviewEventSender = Dependency.get(OverviewProxyService.class);
-    }
-
-    private final Runnable mClickThroughSendTap = new Runnable() {
-        @Override
-        public void run() {
-            sendTap(mClickThroughPressX, mClickThroughPressY);
-            mNavigationBarView.postDelayed(mClickThroughResetTap, CLICK_THROUGH_TAP_RESET_DELAY);
-        }
-    };
-
-    private final Runnable mClickThroughResetTap = () -> {
-        mNavigationBarView.setWindowTouchable(true);
-        mClickThroughPressed = false;
-    };
-
-    public void setComponents(NavigationBarView navigationBarView) {
-        mNavigationBarView = navigationBarView;
-
-        mNavigationBarView.getBackButton().setVisibility(shouldHideBackButton(mContext)
-                ? View.GONE
-                : View.VISIBLE);
-    }
-
-    /**
-     * Set each gesture an action. After set the gestures triggered will run the actions attached.
-     * @param swipeUpAction action after swiping up
-     * @param swipeDownAction action after swiping down
-     * @param swipeLeftAction action after swiping left
-     * @param swipeRightAction action after swiping right
-     * @param swipeLeftFromEdgeAction action swiping left starting from the right side
-     * @param swipeRightFromEdgeAction action swiping right starting from the left side
-     */
-    public void setGestureActions(@Nullable NavigationGestureAction swipeUpAction,
-            @Nullable NavigationGestureAction swipeDownAction,
-            @Nullable NavigationGestureAction swipeLeftAction,
-            @Nullable NavigationGestureAction swipeRightAction,
-            @Nullable NavigationGestureAction swipeLeftFromEdgeAction,
-            @Nullable NavigationGestureAction swipeRightFromEdgeAction) {
-        mGestureActions[ACTION_SWIPE_UP_INDEX] = swipeUpAction;
-        mGestureActions[ACTION_SWIPE_DOWN_INDEX] = swipeDownAction;
-        mGestureActions[ACTION_SWIPE_LEFT_INDEX] = swipeLeftAction;
-        mGestureActions[ACTION_SWIPE_RIGHT_INDEX] = swipeRightAction;
-        mGestureActions[ACTION_SWIPE_LEFT_FROM_EDGE_INDEX] = swipeLeftFromEdgeAction;
-        mGestureActions[ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX] = swipeRightFromEdgeAction;
-
-        // Set the current state to all actions
-        for (NavigationGestureAction action: mGestureActions) {
-            if (action != null) {
-                action.setBarState(true, mNavBarPosition, mDragHPositive, mDragVPositive);
-                action.onDarkIntensityChange(mDarkIntensity);
-                action.onLayout(true /* changed */, mLastLayoutRect.left, mLastLayoutRect.top,
-                        mLastLayoutRect.right, mLastLayoutRect.bottom);
-            }
-        }
-    }
-
-    /**
-     * @return true if we want to intercept touch events for quick scrub and prevent proxying the
-     *         event to the overview service.
-     */
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent event) {
-        return handleTouchEvent(event);
-    }
-
-    /**
-     * @return true if we want to handle touch events for quick scrub or if down event (that will
-     *         get consumed and ignored). No events will be proxied to the overview service.
-     */
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        // The same down event was just sent on intercept and therefore can be ignored here
-        final boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN
-                && mOverviewEventSender.getProxy() != null
-                && mNavigationBarView.getWindowTarget() == WINDOW_TARGET_BOTTOM;
-        return ignoreProxyDownEvent || handleTouchEvent(event);
-    }
-
-    private boolean handleTouchEvent(MotionEvent event) {
-        final boolean deadZoneConsumed =
-                mNavigationBarView.getDownHitTarget() == HIT_TARGET_DEAD_ZONE;
-
-        // Requires proxy and an active gesture or able to perform any gesture to continue
-        if (mOverviewEventSender.getProxy() == null
-                || !mOverviewEventSender.shouldShowSwipeUpUI()
-                || (mCurrentAction == null && !canPerformAnyAction())) {
-            return deadZoneConsumed;
-        }
-        mNavigationBarView.requestUnbufferedDispatch(event);
-
-        int action = event.getActionMasked();
-        switch (action) {
-            case MotionEvent.ACTION_DOWN: {
-                int x = (int) event.getX();
-                int y = (int) event.getY();
-                mIsInScreenPinning = mNavigationBarView.inScreenPinning();
-
-                for (NavigationGestureAction gestureAction: mGestureActions) {
-                    if (gestureAction != null) {
-                        gestureAction.reset();
-                    }
-                }
-
-                // Valid buttons to drag over
-                switch (mNavigationBarView.getDownHitTarget()) {
-                    case HIT_TARGET_BACK:
-                        mHitTarget = mNavigationBarView.getBackButton();
-                        break;
-                    case HIT_TARGET_HOME:
-                        mHitTarget = mNavigationBarView.getHomeButton();
-                        break;
-                    case HIT_TARGET_OVERVIEW:
-                        mHitTarget = mNavigationBarView.getRecentsButton();
-                        break;
-                    default:
-                        mHitTarget = null;
-                        break;
-                }
-                if (mHitTarget != null) {
-                    // Pre-emptively delay the touch feedback for the button that we just touched
-                    mHitTarget.setDelayTouchFeedback(true);
-                }
-                mTouchDownX = x;
-                mTouchDownY = y;
-                mGestureHorizontalDragsButton = false;
-                mGestureVerticalDragsButton = false;
-                mTransformGlobalMatrix.set(Matrix.IDENTITY_MATRIX);
-                mTransformLocalMatrix.set(Matrix.IDENTITY_MATRIX);
-                mNavigationBarView.transformMatrixToGlobal(mTransformGlobalMatrix);
-                mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix);
-                mAllowGestureDetection = true;
-                mNotificationsVisibleOnDown = !mNavigationBarView.isNotificationsFullyCollapsed();
-                mGestureRegionThreshold = QuickStepContract.getEdgeSensitivityWidth(mContext);
-                break;
-            }
-            case MotionEvent.ACTION_MOVE: {
-                if (!mAllowGestureDetection
-                        || mNavigationBarView.getWindowTarget() != WINDOW_TARGET_BOTTOM) {
-                    break;
-                }
-                int x = (int) event.getX();
-                int y = (int) event.getY();
-                int xDiff = Math.abs(x - mTouchDownX);
-                int yDiff = Math.abs(y - mTouchDownY);
-
-                boolean exceededSwipeHorizontalTouchSlop, exceededSwipeVerticalTouchSlop,
-                        exceededSwipeVerticalDragSlop;
-                int posH, touchDownH, posV, touchDownV;
-
-                if (isNavBarVertical()) {
-                    exceededSwipeHorizontalTouchSlop =
-                            yDiff > NavigationBarCompat.getQuickScrubTouchSlopPx() && yDiff > xDiff;
-                    exceededSwipeVerticalTouchSlop =
-                            xDiff > NavigationBarCompat.getQuickStepTouchSlopPx() && xDiff > yDiff;
-                    exceededSwipeVerticalDragSlop =
-                            xDiff > NavigationBarCompat.getQuickStepDragSlopPx() && xDiff > yDiff;
-                    posH = y;
-                    touchDownH = mTouchDownY;
-                    posV = x;
-                    touchDownV = mTouchDownX;
-                } else {
-                    exceededSwipeHorizontalTouchSlop =
-                            xDiff > NavigationBarCompat.getQuickScrubTouchSlopPx() && xDiff > yDiff;
-                    exceededSwipeVerticalTouchSlop =
-                            yDiff > NavigationBarCompat.getQuickStepTouchSlopPx() && yDiff > xDiff;
-                    exceededSwipeVerticalDragSlop =
-                            yDiff > NavigationBarCompat.getQuickStepDragSlopPx() && yDiff > xDiff;
-                    posH = x;
-                    touchDownH = mTouchDownX;
-                    posV = y;
-                    touchDownV = mTouchDownY;
-                }
-
-                if (mCurrentAction != null) {
-                    // Gesture started, provide positions to the current action
-                    mCurrentAction.onGestureMove(x, y);
-                } else {
-                    // Detect gesture and try to execute an action, only one can run at a time
-                    if (exceededSwipeVerticalTouchSlop || exceededSwipeVerticalDragSlop) {
-                        if (mDragVPositive ? (posV < touchDownV) : (posV > touchDownV)) {
-                            // Swipe up gesture must use the larger slop
-                            if (exceededSwipeVerticalTouchSlop) {
-                                // Swiping up gesture
-                                tryToStartGesture(mGestureActions[ACTION_SWIPE_UP_INDEX],
-                                        false /* alignedWithNavBar */, event);
-                            }
-                        } else {
-                            // Swiping down gesture
-                            tryToStartGesture(mGestureActions[ACTION_SWIPE_DOWN_INDEX],
-                                    false /* alignedWithNavBar */, event);
-                        }
-                    } else if (exceededSwipeHorizontalTouchSlop) {
-                        if (mDragHPositive ? (posH < touchDownH) : (posH > touchDownH)) {
-                            // Swiping left (rtl) gesture
-                            tryToStartGesture(mGestureActions[ACTION_SWIPE_LEFT_INDEX],
-                                    true /* alignedWithNavBar */, event);
-                        } else {
-                            // Swiping right (ltr) gesture
-                            tryToStartGesture(mGestureActions[ACTION_SWIPE_RIGHT_INDEX],
-                                    true /* alignedWithNavBar */, event);
-                        }
-                    }
-                }
-
-                handleDragHitTarget(mGestureHorizontalDragsButton ? posH : posV,
-                        mGestureHorizontalDragsButton ? touchDownH : touchDownV);
-                break;
-            }
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                if (mCurrentAction != null) {
-                    mCurrentAction.endGesture();
-                } else if (action == MotionEvent.ACTION_UP) {
-                    if (canTriggerEdgeSwipe(event)) {
-                        int index = mNavigationBarView.getWindowTarget() == NAV_BAR_LEFT
-                                ? ACTION_SWIPE_RIGHT_FROM_EDGE_INDEX
-                                : ACTION_SWIPE_LEFT_FROM_EDGE_INDEX;
-                        tryToStartGesture(mGestureActions[index], false /* alignedWithNavBar */,
-                                event);
-                        if (mCurrentAction != null) {
-                            mCurrentAction.endGesture();
-                        }
-                    } else if (QuickStepContract.isNavBarClickThrough(mContext)
-                            && !mClickThroughPressed) {
-                        // Enable click through functionality where no gesture has been detected and
-                        // not passed the drag slop so inject a touch event at the same location
-                        // after making the navigation bar window untouchable. After a some time,
-                        // the navigation bar will be able to take input events again
-                        float diffX = Math.abs(event.getX() - mTouchDownX);
-                        float diffY = Math.abs(event.getY() - mTouchDownY);
-
-                        if ((diffX <= NavigationBarCompat.getQuickStepDragSlopPx()
-                                && diffY <= NavigationBarCompat.getQuickStepDragSlopPx())) {
-                            mNavigationBarView.setWindowTouchable(false);
-                            mClickThroughPressX = event.getRawX();
-                            mClickThroughPressY = event.getRawY();
-                            mClickThroughPressed = true;
-                            mNavigationBarView.postDelayed(mClickThroughSendTap,
-                                    CLICK_THROUGH_TAP_DELAY);
-                        }
-                    }
-                }
-
-                // Return the hit target back to its original position
-                if (mHitTarget != null) {
-                    final View button = mHitTarget.getCurrentView();
-                    if (mGestureHorizontalDragsButton || mGestureVerticalDragsButton) {
-                        mDragBtnAnimator = button.animate().setDuration(BACK_BUTTON_FADE_IN_ALPHA)
-                                .setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-                        if (mGestureVerticalDragsButton ^ isNavBarVertical()) {
-                            mDragBtnAnimator.translationY(0);
-                        } else {
-                            mDragBtnAnimator.translationX(0);
-                        }
-                        mDragBtnAnimator.start();
-                    }
-                }
-                break;
-        }
-
-        if (shouldProxyEvents(action)) {
-            proxyMotionEvents(event);
-        }
-
-        // Clear action when gesture and event proxy finishes
-        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
-            mCurrentAction = null;
-        }
-        return mCurrentAction != null || deadZoneConsumed;
-    }
-
-    private void handleDragHitTarget(int position, int touchDown) {
-        // Drag the hit target if gesture action requires it
-        if (mHitTarget != null && (mGestureVerticalDragsButton || mGestureHorizontalDragsButton)) {
-            final View button = mHitTarget.getCurrentView();
-            if (mDragBtnAnimator != null) {
-                mDragBtnAnimator.cancel();
-                mDragBtnAnimator = null;
-            }
-
-            // Clamp drag to the bounding box of the navigation bar
-            float diff = (position - touchDown) * mDragDampeningFactor;
-            diff = Utilities.clamp(diff, mMinDragLimit, mMaxDragLimit);
-            if (mGestureVerticalDragsButton ^ isNavBarVertical()) {
-                button.setTranslationY(diff);
-            } else {
-                button.setTranslationX(diff);
-            }
-        }
-    }
-
-    private boolean shouldProxyEvents(int action) {
-        // Do not send events for side navigation bar panels
-        if (mNavigationBarView.getWindowTarget() != WINDOW_TARGET_BOTTOM) {
-            return false;
-        }
-        final boolean actionValid = (mCurrentAction == null
-                || !mCurrentAction.disableProxyEvents());
-        if (actionValid && !mIsInScreenPinning) {
-            // Allow down, cancel and up events, move and other events are passed if notifications
-            // are not showing and disabled gestures (such as long press) are not executed
-            switch (action) {
-                case MotionEvent.ACTION_DOWN:
-                case MotionEvent.ACTION_CANCEL:
-                case MotionEvent.ACTION_UP:
-                    return true;
-                default:
-                    return !mNotificationsVisibleOnDown && mAllowGestureDetection;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        if (mCurrentAction != null) {
-            mCurrentAction.onDraw(canvas);
-        }
-    }
-
-    @Override
-    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        for (NavigationGestureAction action: mGestureActions) {
-            if (action != null) {
-                action.onLayout(changed, left, top, right, bottom);
-            }
-        }
-        mLastLayoutRect.set(left, top, right, bottom);
-    }
-
-    @Override
-    public void onDarkIntensityChange(float intensity) {
-        final float oldIntensity = mDarkIntensity;
-        mDarkIntensity = intensity;
-
-        // When in quick scrub, invalidate gradient if changing intensity from black to white and
-        // vice-versa
-        if (mCurrentAction != null && mNavigationBarView.isQuickScrubEnabled()
-                && Math.round(intensity) != Math.round(oldIntensity)) {
-            mCurrentAction.onDarkIntensityChange(mDarkIntensity);
-        }
-        mNavigationBarView.invalidate();
-    }
-
-    @Override
-    public void setBarState(boolean isRTL, int navBarPosition) {
-        final boolean changed = (mIsRTL != isRTL) || (mNavBarPosition != navBarPosition);
-        mIsRTL = isRTL;
-        mNavBarPosition = navBarPosition;
-
-        // Determine the drag directions depending on location of nav bar
-        switch (navBarPosition) {
-            case NAV_BAR_LEFT:
-                mDragHPositive = !isRTL;
-                mDragVPositive = false;
-                break;
-            case NAV_BAR_RIGHT:
-                mDragHPositive = isRTL;
-                mDragVPositive = true;
-                break;
-            case NAV_BAR_BOTTOM:
-                mDragHPositive = !isRTL;
-                mDragVPositive = true;
-                break;
-            case NAV_BAR_INVALID:
-                Log.e(TAG, "Invalid nav bar position");
-                break;
-        }
-
-        for (NavigationGestureAction action: mGestureActions) {
-            if (action != null) {
-                action.setBarState(changed, mNavBarPosition, mDragHPositive, mDragVPositive);
-            }
-        }
-    }
-
-    @Override
-    public void onNavigationButtonLongPress(View v) {
-        mAllowGestureDetection = false;
-    }
-
-    @Override
-    public void dump(PrintWriter pw) {
-        pw.println("QuickStepController {");
-        pw.print("    "); pw.println("mAllowGestureDetection=" + mAllowGestureDetection);
-        pw.print("    "); pw.println("mNotificationsVisibleOnDown=" + mNotificationsVisibleOnDown);
-        pw.print("    "); pw.println("mNavBarPosition=" + mNavBarPosition);
-        pw.print("    "); pw.println("mIsRTL=" + mIsRTL);
-        pw.print("    "); pw.println("mIsInScreenPinning=" + mIsInScreenPinning);
-        pw.println("}");
-    }
-
-    public NavigationGestureAction getCurrentAction() {
-        return mCurrentAction;
-    }
-
-    private void tryToStartGesture(NavigationGestureAction action, boolean alignedWithNavBar,
-            MotionEvent event) {
-        if (action == null) {
-            return;
-        }
-        if (mIsInScreenPinning) {
-            mNavigationBarView.showPinningEscapeToast();
-            mAllowGestureDetection = false;
-            return;
-        }
-
-        // Start new action from gesture if is able to start and depending on notifications
-        // visibility and starting touch down target. If the action is enabled, then also check if
-        // can perform the action so that if action requires the button to be dragged, then the
-        // gesture will have a large dampening factor and prevent action from running.
-        final boolean validHitTarget = action.requiresTouchDownHitTarget() == HIT_TARGET_NONE
-                || action.requiresTouchDownHitTarget() == mNavigationBarView.getDownHitTarget();
-        if (mCurrentAction == null && validHitTarget && action.isEnabled()
-                && (!mNotificationsVisibleOnDown || action.canRunWhenNotificationsShowing())) {
-            if (action.canPerformAction()) {
-                mCurrentAction = action;
-                event.transform(mTransformGlobalMatrix);
-                action.startGesture(event);
-                event.transform(mTransformLocalMatrix);
-
-                // Calculate the bounding limits of drag to avoid dragging off nav bar's window
-                if (action.allowHitTargetToMoveOverDrag() && mHitTarget != null) {
-                    final int[] buttonCenter = new int[2];
-                    View button = mHitTarget.getCurrentView();
-                    button.getLocationInWindow(buttonCenter);
-                    buttonCenter[0] += button.getWidth() / 2;
-                    buttonCenter[1] += button.getHeight() / 2;
-                    final int x = isNavBarVertical() ? buttonCenter[1] : buttonCenter[0];
-                    final int y = isNavBarVertical() ? buttonCenter[0] : buttonCenter[1];
-                    final int iconHalfSize = mContext.getResources()
-                            .getDimensionPixelSize(R.dimen.navigation_icon_size) / 2;
-
-                    if (alignedWithNavBar) {
-                        mMinDragLimit =  iconHalfSize - x;
-                        mMaxDragLimit = -x - iconHalfSize + (isNavBarVertical()
-                                ? mNavigationBarView.getHeight() : mNavigationBarView.getWidth());
-                    } else {
-                        mMinDragLimit = iconHalfSize - y;
-                        mMaxDragLimit =  -y - iconHalfSize + (isNavBarVertical()
-                                ? mNavigationBarView.getWidth() : mNavigationBarView.getHeight());
-                    }
-                }
-            }
-
-            // Handle direction of the hit target drag from the axis that started the gesture
-            // Also calculate the dampening factor, weaker dampening if there is an active action
-            if (action.allowHitTargetToMoveOverDrag()) {
-                if (alignedWithNavBar) {
-                    mGestureHorizontalDragsButton = true;
-                    mGestureVerticalDragsButton = false;
-                    mDragDampeningFactor = action.isActive()
-                            ? HORIZONTAL_GESTURE_DAMPING : HORIZONTAL_DISABLED_GESTURE_DAMPING;
-                } else {
-                    mGestureVerticalDragsButton = true;
-                    mGestureHorizontalDragsButton = false;
-                    mDragDampeningFactor = action.isActive()
-                            ? VERTICAL_GESTURE_DAMPING : VERTICAL_DISABLED_GESTURE_DAMPING;
-                }
-            }
-
-            if (mHitTarget != null) {
-                mHitTarget.abortCurrentGesture();
-            }
-        }
-    }
-
-    /**
-     * To trigger an edge swipe, the user must start from the left or right edges of certain height
-     * from the bottom then past the drag slope towards the center of the screen, followed by either
-     * a timed trigger for fast swipes or distance if held on the screen longer.
-     * For time, user must swipe up quickly before the Tap Timeout (typically 100ms) and for
-     * distance, the user can drag back to cancel if the touch up has not past the threshold.
-     * @param event Touch up event
-     * @return whether or not edge swipe gesture occurs
-     */
-    private boolean canTriggerEdgeSwipe(MotionEvent event) {
-        if (mNavigationBarView.getWindowTarget() == WINDOW_TARGET_BOTTOM) {
-            return false;
-        }
-        int x = (int) event.getX();
-        int y = (int) event.getY();
-        int xDiff = Math.abs(x - mTouchDownX);
-        int yDiff = Math.abs(y - mTouchDownY);
-        final boolean exceededSwipeTouchSlop = xDiff > NavigationBarCompat.getQuickStepDragSlopPx()
-                && xDiff > yDiff;
-        if (exceededSwipeTouchSlop) {
-            long timeDiff = event.getEventTime() - event.getDownTime();
-            return xDiff > mGestureRegionThreshold || timeDiff < ViewConfiguration.getTapTimeout();
-        }
-        return false;
-    }
-
-    private boolean canPerformAnyAction() {
-        for (NavigationGestureAction action: mGestureActions) {
-            if (action != null && action.isEnabled()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void sendTap(float x, float y) {
-        long now = SystemClock.uptimeMillis();
-        injectMotionEvent(InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.ACTION_DOWN, now, x, y, 1.0f);
-        injectMotionEvent(InputDevice.SOURCE_TOUCHSCREEN, MotionEvent.ACTION_UP, now, x, y, 0.0f);
-    }
-
-    private int getInputDeviceId(int inputSource) {
-        int[] devIds = InputDevice.getDeviceIds();
-        for (int devId : devIds) {
-            InputDevice inputDev = InputDevice.getDevice(devId);
-            if (inputDev.supportsSource(inputSource)) {
-                return devId;
-            }
-        }
-        return 0;
-    }
-
-    private void injectMotionEvent(int inputSource, int action, long when, float x, float y,
-            float pressure) {
-        final float defaultSize = 1.0f;
-        final int defaultMetaState = 0;
-        final float defaultPrecisionX = 1.0f;
-        final float defaultPrecisionY = 1.0f;
-        final int defaultEdgeFlags = 0;
-        MotionEvent event = MotionEvent.obtain(when, when, action, x, y, pressure, defaultSize,
-                defaultMetaState, defaultPrecisionX, defaultPrecisionY,
-                getInputDeviceId(inputSource), defaultEdgeFlags);
-        event.setSource(inputSource);
-        InputManager.getInstance().injectInputEvent(event,
-                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
-    }
-
-    private boolean proxyMotionEvents(MotionEvent event) {
-        event.transform(mTransformGlobalMatrix);
-        InputEventDispatcher dispatcher = mOverviewEventSender.getInputEventDispatcher();
-        if (dispatcher != null) {
-            dispatcher.dispatch(event);
-        }
-
-        final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
-        try {
-            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-                overviewProxy.onPreMotionEvent(mNavigationBarView.getDownHitTarget());
-            }
-            overviewProxy.onMotionEvent(event);
-            if (DEBUG_OVERVIEW_PROXY) {
-                Log.d(TAG_OPS, "Send MotionEvent: " + event.toString());
-            }
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Callback failed", e);
-        } finally {
-            event.transform(mTransformLocalMatrix);
-        }
-        return false;
-    }
-
-    protected boolean isNavBarVertical() {
-        return mNavBarPosition == NAV_BAR_LEFT || mNavBarPosition == NAV_BAR_RIGHT;
-    }
-
-    private static int convertDpToPixel(float dp) {
-        return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
-    }
-
-    // TODO(112934365): Clean up following methods when cleaning up nav bar experiments
-
-    static boolean getBoolGlobalSetting(Context context, String key) {
-        return Settings.Global.getInt(context.getContentResolver(), key, 0) != 0;
-    }
-
-    static int getIntGlobalSetting(Context context, String key, int defaultValue) {
-        return Settings.Global.getInt(context.getContentResolver(), key, defaultValue);
-    }
-
-    /**
-     * @return whether to hide the back button.
-     */
-    public static boolean shouldHideBackButton(Context context) {
-        return QuickStepContract.isGesturalMode(context);
-    }
-
-    /**
-     * @return whether to hide the home button.
-     */
-    public static boolean shouldHideHomeButton(Context context) {
-        return QuickStepContract.isGesturalMode(context);
-    }
-
-    /**
-     * @return whether to show the home handle.
-     */
-    public static boolean showHomeHandle(Context context) {
-        return QuickStepContract.isGesturalMode(context);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSwitchAction.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSwitchAction.java
deleted file mode 100644
index 974de4b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSwitchAction.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.recents.OverviewProxyService.DEBUG_OVERVIEW_PROXY;
-import static com.android.systemui.recents.OverviewProxyService.TAG_OPS;
-
-import android.annotation.NonNull;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.shared.recents.utilities.Utilities;
-
-/**
- * QuickSwitch action to send to launcher
- */
-public class QuickSwitchAction extends NavigationGestureAction {
-    private static final String TAG = "QuickSwitchAction";
-
-    protected final Rect mDragOverRect = new Rect();
-
-    public QuickSwitchAction(@NonNull NavigationBarView navigationBar,
-            @NonNull OverviewProxyService service) {
-        super(navigationBar, service);
-    }
-
-    @Override
-    public void setBarState(boolean changed, int navBarPos, boolean dragHorPositive,
-            boolean dragVerPositive) {
-        super.setBarState(changed, navBarPos, dragHorPositive, dragVerPositive);
-        if (changed && isActive()) {
-            // End quickscrub if the state changes mid-transition
-            endQuickGesture(false /* animate */);
-        }
-    }
-
-    @Override
-    public boolean isEnabled() {
-        return mNavigationBarView.isQuickScrubEnabled();
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        mDragOverRect.set(top, left, right, bottom);
-    }
-
-    @Override
-    public boolean disableProxyEvents() {
-        return true;
-    }
-
-    @Override
-    protected void onGestureStart(MotionEvent event) {
-        startQuickGesture(event);
-    }
-
-    @Override
-    public void onGestureMove(int x, int y) {
-        int dragLength, offset;
-        if (isNavBarVertical()) {
-            dragLength = mDragOverRect.height();
-            offset = y - mDragOverRect.top;
-        } else {
-            offset = x - mDragOverRect.left;
-            dragLength = mDragOverRect.width();
-        }
-        if (!mDragHorizontalPositive || !mDragVerticalPositive) {
-            offset -= dragLength;
-        }
-        float scrubFraction = Utilities.clamp(Math.abs(offset) * 1f / dragLength, 0, 1);
-        try {
-            mProxySender.getProxy().onQuickScrubProgress(scrubFraction);
-            if (DEBUG_OVERVIEW_PROXY) {
-                Log.d(TAG_OPS, "Quick Switch Progress:" + scrubFraction);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to send progress of quick switch.", e);
-        }
-    }
-
-    @Override
-    protected void onGestureEnd() {
-        endQuickGesture(true /* animate */);
-    }
-
-    protected void startQuickGesture(MotionEvent event) {
-        // Disable slippery for quick scrub to not cancel outside the nav bar
-        mNavigationBarView.updateSlippery();
-
-        try {
-            mProxySender.getProxy().onQuickScrubStart();
-            if (DEBUG_OVERVIEW_PROXY) {
-                Log.d(TAG_OPS, "Quick Scrub Start");
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to send start of quick scrub.", e);
-        }
-        mProxySender.notifyQuickScrubStarted();
-    }
-
-    protected void endQuickGesture(boolean animate) {
-        try {
-            mProxySender.getProxy().onQuickScrubEnd();
-            if (DEBUG_OVERVIEW_PROXY) {
-                Log.d(TAG_OPS, "Quick Scrub End");
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to send end of quick scrub.", e);
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index ed66b4b..e5defae1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -2484,6 +2484,14 @@
                 options.setRotationAnimationHint(
                         WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
             }
+            if (intent.getAction() == Settings.Panel.ACTION_VOLUME) {
+                // Settings Panel is implemented as activity(not a dialog), so
+                // underlying app is paused and may enter picture-in-picture mode
+                // as a result.
+                // So we need to disable picture-in-picture mode here
+                // if it is volume panel.
+                options.setDisallowEnterPictureInPictureWhileLaunching(true);
+            }
             try {
                 result = ActivityTaskManager.getService().startActivityAsUser(
                         null, mContext.getBasePackageName(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 3b32d95..92cd280 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -419,6 +419,7 @@
         } else if (finishRunnable != null) {
             finishRunnable.run();
         }
+        mNotificationPanelView.blockExpansionForCurrentTouch();
     }
 
     /**
@@ -572,6 +573,10 @@
         return mBouncer.isShowing();
     }
 
+    public boolean isBouncerPartiallyVisible() {
+        return mBouncer.isPartiallyVisible();
+    }
+
     public boolean isFullscreenBouncer() {
         return mBouncer.isFullscreenBouncer();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
index bdd76c8..44c82c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockMethodCache.java
@@ -43,7 +43,6 @@
     /** Whether the unlock method is currently insecure (insecure method or trusted environment) */
     private boolean mCanSkipBouncer;
     private boolean mTrustManaged;
-    private boolean mFaceUnlockRunning;
     private boolean mTrusted;
 
     private UnlockMethodCache(Context ctx) {
@@ -93,16 +92,13 @@
         boolean canSkipBouncer = !secure ||  mKeyguardUpdateMonitor.getUserCanSkipBouncer(user);
         boolean trustManaged = mKeyguardUpdateMonitor.getUserTrustIsManaged(user);
         boolean trusted = mKeyguardUpdateMonitor.getUserHasTrust(user);
-        boolean faceUnlockRunning = mKeyguardUpdateMonitor.isFaceUnlockRunning(user)
-                && trustManaged;
         boolean changed = secure != mSecure || canSkipBouncer != mCanSkipBouncer ||
-                trustManaged != mTrustManaged  || faceUnlockRunning != mFaceUnlockRunning;
+                trustManaged != mTrustManaged;
         if (changed || updateAlways) {
             mSecure = secure;
             mCanSkipBouncer = canSkipBouncer;
             mTrusted = trusted;
             mTrustManaged = trustManaged;
-            mFaceUnlockRunning = faceUnlockRunning;
             notifyListeners();
         }
         Trace.endSection();
@@ -171,10 +167,6 @@
         return mTrustManaged;
     }
 
-    public boolean isFaceUnlockRunning() {
-        return mFaceUnlockRunning;
-    }
-
     public static interface OnUnlockMethodChangedListener {
         void onUnlockMethodStateChanged();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
index 97be6ed..98cde2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastController.java
@@ -19,12 +19,12 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.statusbar.policy.CastController.Callback;
 
-import java.util.Set;
+import java.util.List;
 
 public interface CastController extends CallbackController<Callback>, Dumpable {
     void setDiscovering(boolean request);
     void setCurrentUserId(int currentUserId);
-    Set<CastDevice> getCastDevices();
+    List<CastDevice> getCastDevices();
     void startCasting(CastDevice device);
     void stopCasting(CastDevice device);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index c7d337a..505dd16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -29,7 +29,6 @@
 import android.os.Handler;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
@@ -40,8 +39,8 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
-import java.util.Set;
 import java.util.UUID;
 
 import javax.inject.Inject;
@@ -150,8 +149,31 @@
     }
 
     @Override
-    public Set<CastDevice> getCastDevices() {
-        final ArraySet<CastDevice> devices = new ArraySet<CastDevice>();
+    public List<CastDevice> getCastDevices() {
+        final ArrayList<CastDevice> devices = new ArrayList<>();
+        synchronized(mRoutes) {
+            for (RouteInfo route : mRoutes.values()) {
+                final CastDevice device = new CastDevice();
+                device.id = route.getTag().toString();
+                final CharSequence name = route.getName(mContext);
+                device.name = name != null ? name.toString() : null;
+                final CharSequence description = route.getDescription();
+                device.description = description != null ? description.toString() : null;
+
+                int statusCode = route.getStatusCode();
+                if (statusCode == RouteInfo.STATUS_CONNECTING) {
+                    device.state = CastDevice.STATE_CONNECTING;
+                } else if (route.isSelected() || statusCode == RouteInfo.STATUS_CONNECTED) {
+                    device.state = CastDevice.STATE_CONNECTED;
+                } else {
+                    device.state = CastDevice.STATE_DISCONNECTED;
+                }
+
+                device.tag = route;
+                devices.add(device);
+            }
+        }
+
         synchronized (mProjectionLock) {
             if (mProjection != null) {
                 final CastDevice device = new CastDevice();
@@ -161,24 +183,9 @@
                 device.state = CastDevice.STATE_CONNECTED;
                 device.tag = mProjection;
                 devices.add(device);
-                return devices;
             }
         }
-        synchronized(mRoutes) {
-            for (RouteInfo route : mRoutes.values()) {
-                final CastDevice device = new CastDevice();
-                device.id = route.getTag().toString();
-                final CharSequence name = route.getName(mContext);
-                device.name = name != null ? name.toString() : null;
-                final CharSequence description = route.getDescription();
-                device.description = description != null ? description.toString() : null;
-                device.state = route.isConnecting() ? CastDevice.STATE_CONNECTING
-                        : route.isSelected() ? CastDevice.STATE_CONNECTED
-                        : CastDevice.STATE_DISCONNECTED;
-                device.tag = route;
-                devices.add(device);
-            }
-        }
+
         return devices;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 1e05983..06fc745 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -308,6 +308,10 @@
         // TODO(b/122195391): Added logs to make sure sysui is sending back button events
         if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
             Log.i(TAG, "Back button event: " + KeyEvent.actionToString(action));
+            if (action == MotionEvent.ACTION_UP) {
+                mOverviewProxyService.notifyBackAction((flags & KeyEvent.FLAG_CANCELED) == 0,
+                        -1, -1, true /* isButton */, false /* gestureSwipeLeft */);
+            }
         }
         final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
         final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 1e09063..c1950a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -146,7 +146,11 @@
         Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         RemoteInput.addResultsToIntent(mRemoteInputs, fillInIntent,
                 results);
-        RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT);
+        if (mEntry.editedSuggestionInfo == null) {
+            RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_FREE_FORM_INPUT);
+        } else {
+            RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE);
+        }
 
         mEditText.setEnabled(false);
         mSendButton.setVisibility(INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 9343bf1..f726321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -403,7 +403,7 @@
         final int N = mUsers.size();
         for (int i = 0; i < N; ++i) {
             UserRecord record = mUsers.get(i);
-            if (record.info != null && record.info.supportsSwitchTo()) {
+            if (record.info != null && record.info.supportsSwitchToByUser()) {
                 count++;
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 7705e4e..376c328 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -24,6 +24,7 @@
 import android.view.View;
 
 import com.android.keyguard.KeyguardClockSwitch;
+import com.android.keyguard.KeyguardSliceView;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.qs.QSCarrierGroup;
 import com.android.systemui.qs.QSFooterImpl;
@@ -136,6 +137,11 @@
          * Creates the KeyguardClockSwitch.
          */
         KeyguardClockSwitch createKeyguardClockSwitch();
+
+        /**
+         * Creates the KeyguardSliceView.
+         */
+        KeyguardSliceView createKeyguardSliceView();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CaptionsToggleImageButton.java b/packages/SystemUI/src/com/android/systemui/volume/CaptionsToggleImageButton.java
index 8ec66e4..45fc756 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CaptionsToggleImageButton.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CaptionsToggleImageButton.java
@@ -17,7 +17,10 @@
 package com.android.systemui.volume;
 
 import android.content.Context;
+import android.os.Handler;
 import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
 
 import com.android.keyguard.AlphaOptimizedImageButton;
 import com.android.systemui.R;
@@ -27,14 +30,34 @@
 
     private static final int[] OPTED_OUT_STATE = new int[] { R.attr.optedOut };
 
+    private ConfirmedTapListener mConfirmedTapListener;
     private boolean mComponentEnabled = false;
     private boolean mOptedOut = false;
 
+    private GestureDetector mGestureDetector;
+    private GestureDetector.SimpleOnGestureListener mGestureListener =
+            new GestureDetector.SimpleOnGestureListener() {
+        @Override
+        public boolean onSingleTapConfirmed(MotionEvent e) {
+            if (mConfirmedTapListener != null) {
+                mConfirmedTapListener.onConfirmedTap();
+                return true;
+            }
+            return false;
+        }
+    };
+
     public CaptionsToggleImageButton(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mGestureDetector != null) mGestureDetector.onTouchEvent(event);
+        return super.onTouchEvent(event);
+    }
+
+    @Override
     public int[] onCreateDrawableState(int extraSpace) {
         int[] state = super.onCreateDrawableState(extraSpace + 1);
         if (mOptedOut) {
@@ -64,4 +87,17 @@
     boolean getOptedOut() {
         return this.mOptedOut;
     }
+
+    void setOnConfirmedTapListener(ConfirmedTapListener listener, Handler handler) {
+        mConfirmedTapListener = listener;
+
+        if (mGestureDetector == null) {
+            this.mGestureDetector = new GestureDetector(getContext(), mGestureListener, handler);
+        }
+    }
+
+    /** Listener for confirmed taps rather than normal on click listener. */
+    interface ConfirmedTapListener {
+        void onConfirmedTap();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 35a2450..2094b36 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -419,8 +419,9 @@
             row.header.setFilters(new InputFilter[] {new InputFilter.LengthFilter(13)});
         }
         row.dndIcon = row.view.findViewById(R.id.dnd_icon);
-        row.slider =  row.view.findViewById(R.id.volume_row_slider);
+        row.slider = row.view.findViewById(R.id.volume_row_slider);
         row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
+
         row.anim = null;
 
         row.icon = row.view.findViewById(R.id.volume_row_icon);
@@ -515,11 +516,11 @@
 
     private void initODICaptionsH() {
         if (mODICaptionsIcon != null) {
-            mODICaptionsIcon.setOnClickListener(v -> {
+            mODICaptionsIcon.setOnConfirmedTapListener(() -> {
                 onCaptionIconClicked();
                 Events.writeEvent(mContext, Events.EVENT_ODI_CAPTIONS_CLICK);
                 dismissH(DISMISS_REASON_ODI_CAPTIONS_CLICKED);
-            });
+            }, mHandler);
         }
 
         mController.getCaptionsComponentState(false);
@@ -1393,6 +1394,7 @@
             if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
                 mRow.userAttempt = SystemClock.uptimeMillis();
                 if (mRow.requestedLevel != userLevel) {
+                    mController.setActiveStream(mRow.stream);
                     mController.setStreamVolume(mRow.stream, userLevel);
                     mRow.requestedLevel = userLevel;
                     Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream,
diff --git a/packages/SystemUI/tests/Android.mk b/packages/SystemUI/tests/Android.mk
index 83ec33c..5412cde 100644
--- a/packages/SystemUI/tests/Android.mk
+++ b/packages/SystemUI/tests/Android.mk
@@ -71,8 +71,8 @@
 # This appends a * to all classes and replace the space separators with commas.
 jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
 
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.systemui.*
-LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := com.android.systemui.tests.*,$(jacoco_exclude)
+LOCAL_JACK_COVERAGE_INCLUDE_FILTER := com.android.systemui.*,com.android.keyguard.*
+LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
 
 ifeq ($(EXCLUDE_SYSTEMUI_TESTS),)
     include $(BUILD_PACKAGE)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 632b0c0..f01c0b4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -211,6 +211,19 @@
     }
 
     @Test
+    public void onPluginDisconnected_onDestroyView() {
+        // GIVEN a plugin is connected
+        ClockPlugin clockPlugin = mock(ClockPlugin.class);
+        when(clockPlugin.getView()).thenReturn(new TextClock(getContext()));
+        ClockManager.ClockChangedListener listener = mKeyguardClockSwitch.getClockChangedListener();
+        listener.onClockChanged(clockPlugin);
+        // WHEN the plugin is disconnected
+        listener.onClockChanged(null);
+        // THEN onDestroyView is called on the plugin
+        verify(clockPlugin).onDestroyView();
+    }
+
+    @Test
     public void setTextColor_defaultClockSetTextColor() {
         mKeyguardClockSwitch.setTextColor(Color.YELLOW);
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 190ce75..b3accbc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -27,8 +27,10 @@
 import androidx.slice.SliceSpecs;
 import androidx.slice.builders.ListBuilder;
 
+import com.android.systemui.SystemUIFactory;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.util.InjectionInflationController;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -49,7 +51,11 @@
     @Before
     public void setUp() throws Exception {
         com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
-        mKeyguardSliceView = (KeyguardSliceView) LayoutInflater.from(getContext())
+        InjectionInflationController inflationController = new InjectionInflationController(
+                SystemUIFactory.getInstance().getRootComponent());
+        LayoutInflater layoutInflater = inflationController
+                .injectable(LayoutInflater.from(getContext()));
+        mKeyguardSliceView = (KeyguardSliceView) layoutInflater
                 .inflate(R.layout.keyguard_status_area, null);
         mSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);
         SliceProvider.setSpecs(new HashSet<>(Collections.singletonList(SliceSpecs.LIST)));
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 9fa85d3..5e16721 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -18,9 +18,12 @@
 
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -42,7 +45,6 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.statusbar.NotificationTestHelper;
@@ -96,9 +98,9 @@
     private NotificationTestHelper mNotificationTestHelper;
     private ExpandableNotificationRow mRow;
     private ExpandableNotificationRow mRow2;
-    private ExpandableNotificationRow mNoChannelRow;
     private ExpandableNotificationRow mAutoExpandRow;
     private ExpandableNotificationRow mSuppressNotifRow;
+    private ExpandableNotificationRow mNonBubbleNotifRow;
 
     @Mock
     private NotificationData mNotificationData;
@@ -107,9 +109,6 @@
     @Mock
     private BubbleController.BubbleExpandListener mBubbleExpandListener;
     @Mock
-    NotificationVisibility mNotificationVisibility;
-
-    @Mock
     private PendingIntent mDeleteIntent;
 
     private BubbleData mBubbleData;
@@ -129,7 +128,7 @@
         mNotificationTestHelper = new NotificationTestHelper(mContext);
         mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
         mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
-        mNoChannelRow = mNotificationTestHelper.createBubble(mDeleteIntent);
+        mNonBubbleNotifRow = mNotificationTestHelper.createRow();
 
         // Some bubbles want to auto expand
         Notification.BubbleMetadata autoExpandMetadata =
@@ -146,7 +145,6 @@
         // Return non-null notification data from the NEM
         when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData);
         when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel);
-        when(mNotificationData.getChannel(mNoChannelRow.getEntry().key)).thenReturn(null);
 
         mBubbleData = new BubbleData();
         mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController,
@@ -391,8 +389,7 @@
         mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
         mBubbleController.updateBubble(mSuppressNotifRow.getEntry(), true /* updatePosition */);
 
-        // Should be a bubble & should show in shade because we weren't forground
-        assertTrue(mSuppressNotifRow.getEntry().isBubble());
+        // Should show in shade because we weren't forground
         assertTrue(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
 
         // # of bubbles should change
@@ -428,8 +425,7 @@
         mEntryListener.onPendingEntryAdded(mSuppressNotifRow.getEntry());
         mBubbleController.updateBubble(mSuppressNotifRow.getEntry(), true /* updatePosition */);
 
-        // Should be a bubble & should NOT show in shade because we were foreground
-        assertTrue(mSuppressNotifRow.getEntry().isBubble());
+        // Should NOT show in shade because we were foreground
         assertFalse(mSuppressNotifRow.getEntry().showInShadeWhenBubble());
 
         // # of bubbles should change
@@ -444,8 +440,6 @@
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
         mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
 
-        assertTrue(mRow.getEntry().isBubble());
-
         // Simulate notification cancellation.
         mEntryListener.onEntryRemoved(mRow.getEntry(), null /* notificationVisibility (unused) */,
                 false /* removedbyUser */);
@@ -454,18 +448,21 @@
     }
 
     @Test
-    public void testMarkNewNotificationAsBubble() {
-        mEntryListener.onPendingEntryAdded(mRow.getEntry());
-        assertTrue(mRow.getEntry().isBubble());
-    }
-
-    @Test
     public void testMarkNewNotificationAsShowInShade() {
         mEntryListener.onPendingEntryAdded(mRow.getEntry());
         assertTrue(mRow.getEntry().showInShadeWhenBubble());
     }
 
     @Test
+    public void testAddNotif_notBubble() {
+        mEntryListener.onPendingEntryAdded(mNonBubbleNotifRow.getEntry());
+        mEntryListener.onPreEntryUpdated(mNonBubbleNotifRow.getEntry());
+
+        verify(mBubbleStateChangeListener, never()).onHasBubblesChanged(anyBoolean());
+        assertThat(mBubbleController.hasBubbles()).isFalse();
+    }
+
+    @Test
     public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
         mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
         mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_AGED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleStackViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleStackViewTest.java
new file mode 100644
index 0000000..801308f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleStackViewTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bubbles;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class BubbleStackViewTest extends SysuiTestCase {
+    private BubbleStackView mStackView;
+    @Mock private Bubble mBubble;
+    @Mock private NotificationEntry mNotifEntry;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mStackView = new BubbleStackView(mContext, new BubbleData(), null);
+        mBubble.entry = mNotifEntry;
+    }
+
+    @Test
+    public void testAnimateInFlyoutForBubble() throws InterruptedException {
+        when(mNotifEntry.getUpdateMessage(any())).thenReturn("Test Flyout Message.");
+        mStackView.animateInFlyoutForBubble(mBubble);
+
+        // Wait for the fade in.
+        Thread.sleep(200);
+
+        // Flyout should be visible and showing our text.
+        assertEquals(1f, mStackView.findViewById(R.id.bubble_flyout).getAlpha(), .01f);
+        assertEquals("Test Flyout Message.",
+                ((TextView) mStackView.findViewById(R.id.bubble_flyout_text)).getText());
+
+        // Wait until it should have gone away.
+        Thread.sleep(BubbleStackView.FLYOUT_HIDE_AFTER + 200);
+
+        // Flyout should be gone.
+        assertEquals(View.GONE, mStackView.findViewById(R.id.bubble_flyout).getVisibility());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
index 50fadef..910cee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -57,6 +57,7 @@
      * direction is correct.
      */
     @Test
+    @Ignore("Flaking")
     public void testMoveFirstBubbleWithStackFollowing() throws InterruptedException {
         mStackController.moveFirstBubbleWithStackFollowing(200, 100);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index ea8d4b2..818db87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -14,13 +14,19 @@
 
 package com.android.systemui.qs.tiles;
 
+import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertEquals;
 
+import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.media.MediaRouter;
+import android.media.MediaRouter.RouteInfo;
+import android.media.projection.MediaProjectionInfo;
 import android.service.quicksettings.Tile;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -33,6 +39,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.qs.QSTileHost;
 import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.CastController.CastDevice;
 import com.android.systemui.statusbar.policy.KeyguardMonitor;
 import com.android.systemui.statusbar.policy.NetworkController;
 
@@ -43,8 +50,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.HashSet;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.List;
+
 
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -93,7 +101,6 @@
         verify(mNetworkController).observe(any(LifecycleOwner.class),
                 signalCallbackArgumentCaptor.capture());
         mCallback = signalCallbackArgumentCaptor.getValue();
-
     }
 
     @Test
@@ -120,33 +127,125 @@
         assertEquals(Tile.STATE_UNAVAILABLE, mCastTile.getState().state);
     }
 
-    @Test
-    public void testStateActive_wifiEnabledAndCasting() {
-        CastController.CastDevice device = mock(CastController.CastDevice.class);
-        device.state = CastController.CastDevice.STATE_CONNECTED;
-        Set<CastController.CastDevice> devices = new HashSet<>();
-        devices.add(device);
-        when(mController.getCastDevices()).thenReturn(devices);
-
+    private void enableWifiAndProcessMessages() {
         NetworkController.IconState qsIcon =
                 new NetworkController.IconState(true, 0, "");
         mCallback.setWifiIndicators(true, mock(NetworkController.IconState.class),
                 qsIcon, false,false, "",
                 false, "");
         mTestableLooper.processAllMessages();
+    }
 
+    @Test
+    public void testStateActive_wifiEnabledAndCasting() {
+        CastController.CastDevice device = new CastController.CastDevice();
+        device.state = CastController.CastDevice.STATE_CONNECTED;
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(device);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        enableWifiAndProcessMessages();
         assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
     }
 
     @Test
     public void testStateInactive_wifiEnabledNotCasting() {
-        NetworkController.IconState qsIcon =
-                new NetworkController.IconState(true, 0, "");
-        mCallback.setWifiIndicators(true, mock(NetworkController.IconState.class),
-                qsIcon, false,false, "",
-                false, "");
+        enableWifiAndProcessMessages();
+        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+    }
+
+    @Test
+    public void testHandleClick_castDevicePresent() {
+        CastController.CastDevice device = new CastController.CastDevice();
+        device.state = CastDevice.STATE_CONNECTED;
+        device.tag = mock(MediaRouter.RouteInfo.class);
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(device);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        enableWifiAndProcessMessages();
+        mCastTile.handleClick();
         mTestableLooper.processAllMessages();
 
-        assertEquals(Tile.STATE_INACTIVE, mCastTile.getState().state);
+        verify(mActivityStarter, times(1)).postQSRunnableDismissingKeyguard(any());
+    }
+
+    @Test
+    public void testHandleClick_projectionOnly() {
+        CastController.CastDevice device = new CastController.CastDevice();
+        device.state = CastDevice.STATE_CONNECTED;
+        device.tag = mock(MediaProjectionInfo.class);
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(device);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        enableWifiAndProcessMessages();
+        mCastTile.handleClick();
+        mTestableLooper.processAllMessages();
+
+        verify(mController, times(1)).stopCasting(same(device));
+    }
+
+    @Test
+    public void testUpdateState_projectionOnly() {
+        CastController.CastDevice device = new CastController.CastDevice();
+        device.state = CastDevice.STATE_CONNECTED;
+        device.tag = mock(MediaProjectionInfo.class);
+        device.name = "Test Projection Device";
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(device);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        enableWifiAndProcessMessages();
+        assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
+        assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(device.name));
+    }
+
+    @Test
+    public void testUpdateState_castingAndProjection() {
+        CastController.CastDevice casting = new CastController.CastDevice();
+        casting.state = CastDevice.STATE_CONNECTED;
+        casting.tag = mock(RouteInfo.class);
+        casting.name = "Test Casting Device";
+
+        CastController.CastDevice projection = new CastController.CastDevice();
+        projection.state = CastDevice.STATE_CONNECTED;
+        projection.tag = mock(MediaProjectionInfo.class);
+        projection.name = "Test Projection Device";
+
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(casting);
+        devices.add(projection);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        enableWifiAndProcessMessages();
+
+        // Note here that the tile should be active, and should choose casting over projection.
+        assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
+        assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(casting.name));
+    }
+
+    @Test
+    public void testUpdateState_connectedAndConnecting() {
+        CastController.CastDevice connecting = new CastController.CastDevice();
+        connecting.state = CastDevice.STATE_CONNECTING;
+        connecting.tag = mock(RouteInfo.class);
+        connecting.name = "Test Casting Device";
+
+        CastController.CastDevice connected = new CastController.CastDevice();
+        connected.state = CastDevice.STATE_CONNECTED;
+        connected.tag = mock(RouteInfo.class);
+        connected.name = "Test Casting Device";
+
+        List<CastDevice> devices = new ArrayList<>();
+        devices.add(connecting);
+        devices.add(connected);
+        when(mController.getCastDevices()).thenReturn(devices);
+
+        enableWifiAndProcessMessages();
+
+        // Tile should be connected and always prefer the connected device.
+        assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
+        assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(connected.name));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 7e089a6..8cc1571 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar;
 
+import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 
@@ -154,9 +155,7 @@
      */
     public ExpandableNotificationRow createBubble()
             throws Exception {
-        Notification n = createNotification(false /* isGroupSummary */,
-                null /* groupKey */, makeBubbleMetadata(null));
-        return generateRow(n, PKG, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH);
+        return createBubble(makeBubbleMetadata(null), PKG);
     }
 
     /**
@@ -166,21 +165,7 @@
      */
     public ExpandableNotificationRow createBubble(@Nullable PendingIntent deleteIntent)
             throws Exception {
-        Notification n = createNotification(false /* isGroupSummary */,
-                null /* groupKey */, makeBubbleMetadata(deleteIntent));
-        return generateRow(n, PKG, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH);
-    }
-
-    /**
-     * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble.
-     *
-     * @param bubbleMetadata the {@link BubbleMetadata} to use
-     */
-    public ExpandableNotificationRow createBubble(BubbleMetadata bubbleMetadata)
-            throws Exception {
-        Notification n = createNotification(false /* isGroupSummary */,
-                null /* groupKey */, bubbleMetadata);
-        return generateRow(n, PKG, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH);
+        return createBubble(makeBubbleMetadata(deleteIntent), PKG);
     }
 
     /**
@@ -192,6 +177,7 @@
             throws Exception {
         Notification n = createNotification(false /* isGroupSummary */,
                 null /* groupKey */, bubbleMetadata);
+        n.flags |= FLAG_BUBBLE;
         return generateRow(n, pkg, UID, USER_HANDLE, 0 /* extraInflationFlags */, IMPORTANCE_HIGH);
     }
 
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
new file mode 100644
index 0000000..cca9f28
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.os.Bundle;
+import android.service.notification.StatusBarNotification;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationEntryTest extends SysuiTestCase {
+    @Mock
+    private StatusBarNotification mStatusBarNotification;
+    @Mock
+    private Notification mNotif;
+
+    private NotificationEntry mEntry;
+    private Bundle mExtras;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mStatusBarNotification.getKey()).thenReturn("key");
+        when(mStatusBarNotification.getNotification()).thenReturn(mNotif);
+
+        mExtras = new Bundle();
+        mNotif.extras = mExtras;
+
+        mEntry = new NotificationEntry(mStatusBarNotification);
+    }
+
+    @Test
+    public void testGetUpdateMessage_default() {
+        final String msg = "Hello there!";
+        doReturn(Notification.Style.class).when(mNotif).getNotificationStyle();
+        mExtras.putCharSequence(Notification.EXTRA_TEXT, msg);
+        assertEquals(msg, mEntry.getUpdateMessage(mContext));
+    }
+
+    @Test
+    public void testGetUpdateMessage_bigText() {
+        final String msg = "A big hello there!";
+        doReturn(Notification.BigTextStyle.class).when(mNotif).getNotificationStyle();
+        mExtras.putCharSequence(Notification.EXTRA_TEXT, "A small hello there.");
+        mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg);
+
+        // Should be big text, not the small text.
+        assertEquals(msg, mEntry.getUpdateMessage(mContext));
+    }
+
+    @Test
+    public void testGetUpdateMessage_media() {
+        doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle();
+
+        // Media notifs don't get update messages.
+        assertNull(mEntry.getUpdateMessage(mContext));
+    }
+
+    @Test
+    public void testGetUpdateMessage_inboxStyle() {
+        doReturn(Notification.InboxStyle.class).when(mNotif).getNotificationStyle();
+        mExtras.putCharSequenceArray(
+                Notification.EXTRA_TEXT_LINES,
+                new CharSequence[]{
+                        "How do you feel about tests?",
+                        "They're okay, I guess.",
+                        "I hate when they're flaky.",
+                        "Really? I prefer them that way."});
+
+        // Should be the last one only.
+        assertEquals("Really? I prefer them that way.", mEntry.getUpdateMessage(mContext));
+    }
+
+    @Test
+    public void testGetUpdateMessage_messagingStyle() {
+        doReturn(Notification.MessagingStyle.class).when(mNotif).getNotificationStyle();
+        mExtras.putParcelableArray(
+                Notification.EXTRA_MESSAGES,
+                new Bundle[]{
+                        new Notification.MessagingStyle.Message(
+                                "Hello", 0, "Josh").toBundle(),
+                        new Notification.MessagingStyle.Message(
+                                "Oh, hello!", 0, "Mady").toBundle()});
+
+        // Should be the last one only.
+        assertEquals("Mady: Oh, hello!", mEntry.getUpdateMessage(mContext));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
index 4fe364a..3c91b3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
@@ -24,11 +25,13 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.NotificationChannel;
 import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -130,6 +133,21 @@
         verify(mGutsManager).openGuts(row, 0, 0, mMenuItem);
     }
 
+    @Test
+    public void testPerhapsShowBlockingHelper_notShownForMultiChannelGroup() throws Exception {
+        ExpandableNotificationRow groupRow = createBlockableGroupRowSpy(10);
+        int i = 0;
+        for (ExpandableNotificationRow childRow : groupRow.getNotificationChildren()) {
+            childRow.getEntry().channel =
+                    new NotificationChannel(Integer.toString(i++), "", IMPORTANCE_DEFAULT);
+        }
+
+        groupRow.getEntry().userSentiment = USER_SENTIMENT_NEGATIVE;
+
+        assertFalse(mBlockingHelperManager.perhapsShowBlockingHelper(groupRow, mMenuRow));
+
+        verify(mGutsManager, never()).openGuts(groupRow, 0, 0, mMenuItem);
+    }
 
     @Test
     public void testPerhapsShowBlockingHelper_shownForLargeGroup() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index ff849c3..8380192 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -325,7 +325,6 @@
                 eq(false),
                 eq(false),
                 eq(true) /* isForBlockingHelper */,
-                eq(true) /* isUserSentimentNegative */,
                 eq(0),
                 eq(false) /* wasShownHighPriority */);
     }
@@ -354,7 +353,6 @@
                 eq(false),
                 eq(false),
                 eq(false) /* isForBlockingHelper */,
-                eq(true) /* isUserSentimentNegative */,
                 eq(0),
                 eq(false) /* wasShownHighPriority */);
     }
@@ -385,7 +383,6 @@
                 eq(false),
                 eq(false),
                 eq(true) /* isForBlockingHelper */,
-                eq(true) /* isUserSentimentNegative */,
                 eq(IMPORTANCE_DEFAULT),
                 eq(true) /* wasShownHighPriority */);
     }
@@ -415,7 +412,6 @@
                 eq(true),
                 eq(false),
                 eq(false) /* isForBlockingHelper */,
-                eq(true) /* isUserSentimentNegative */,
                 eq(0),
                 eq(false) /* wasShownHighPriority */);
     }
@@ -444,7 +440,6 @@
                 eq(false),
                 eq(false),
                 eq(true) /* isForBlockingHelper */,
-                eq(true) /* isUserSentimentNegative */,
                 eq(0),
                 eq(false) /* wasShownHighPriority */);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
index fb4fd31..d2f8e02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java
@@ -313,108 +313,20 @@
     }
 
     @Test
-    public void testBindNotification_BlockButton() throws Exception {
+    public void testBindNotification_BlockLink_BlockingHelper() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT, true);
-        final View block = mNotificationInfo.findViewById(R.id.int_block);
-        final View minimize = mNotificationInfo.findViewById(R.id.block_or_minimize);
-        assertEquals(VISIBLE, block.getVisibility());
-        assertEquals(GONE, minimize.getVisibility());
-    }
-
-    @Test
-    public void testBindNotification_BlockButton_BlockingHelper() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                true /* isBlockingHelper */, false, IMPORTANCE_DEFAULT, true);
-        final View block = mNotificationInfo.findViewById(R.id.block);
+                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, mock(
+                        NotificationInfo.OnSettingsClickListener.class), null, true, false,
+                true /* isBlockingHelper */, IMPORTANCE_DEFAULT, true);
+        final View block =
+                mNotificationInfo.findViewById(R.id.blocking_helper_turn_off_notifications);
         final View interruptivenessSettings = mNotificationInfo.findViewById(
-                R.id.interruptiveness_settings);
+                R.id.inline_controls);
         assertEquals(VISIBLE, block.getVisibility());
         assertEquals(GONE, interruptivenessSettings.getVisibility());
     }
 
     @Test
-    public void testBindNotification_SilenceButton_CurrentlyAlerting() throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT, true);
-        final TextView silent = mNotificationInfo.findViewById(R.id.int_silent_label);
-        assertEquals(VISIBLE, silent.getVisibility());
-        assertEquals(
-                mContext.getString(R.string.inline_silent_button_silent), silent.getText());
-    }
-
-    @Test
-    public void testBindNotification_verifyButtonTexts() throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_LOW, false);
-        final TextView block = mNotificationInfo.findViewById(R.id.int_block_label);
-        final TextView alert = mNotificationInfo.findViewById(R.id.int_alert_label);
-        final TextView silent = mNotificationInfo.findViewById(R.id.int_silent_label);
-        assertEquals(VISIBLE, silent.getVisibility());
-        assertEquals(VISIBLE, block.getVisibility());
-        assertEquals(VISIBLE, alert.getVisibility());
-        assertEquals(
-                mContext.getString(R.string.inline_silent_button_silent),
-                silent.getText());
-        assertEquals(
-                mContext.getString(R.string.inline_silent_button_alert), alert.getText());
-        assertEquals(
-                mContext.getString(R.string.inline_block_button), block.getText());
-    }
-
-    @Test
-    public void testBindNotification_verifyHintTextForSilent() throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_LOW, false);
-        TextView hintText = mNotificationInfo.findViewById(R.id.hint_text);
-        assertEquals(mContext.getString(R.string.hint_text_silent), hintText.getText());
-    }
-
-    @Test
-    public void testBindNotification_verifyHintTextForBlock() throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_LOW, false);
-        View blockWrapper = mNotificationInfo.findViewById(R.id.int_block_wrapper);
-        blockWrapper.performClick();
-        TextView hintText = mNotificationInfo.findViewById(R.id.hint_text);
-        assertEquals(mContext.getString(R.string.hint_text_block), hintText.getText());
-    }
-
-    @Test
-    public void testBindNotification_verifyHintTextForAlert() throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT, true);
-        TextView hintText = mNotificationInfo.findViewById(R.id.hint_text);
-        assertEquals(mContext.getString(R.string.hint_text_alert), hintText.getText());
-    }
-
-    @Test
-    public void testBindNotification_MinButton() throws Exception {
-        mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT, true);
-        final View block = mNotificationInfo.findViewById(R.id.block);
-        final View interruptivenessSettings = mNotificationInfo.findViewById(
-                R.id.interruptiveness_settings);
-        final View minimize = mNotificationInfo.findViewById(R.id.minimize);
-        assertEquals(GONE, block.getVisibility());
-        assertEquals(GONE, interruptivenessSettings.getVisibility());
-        assertEquals(VISIBLE, minimize.getVisibility());
-    }
-
-    @Test
     public void testBindNotification_SetsOnClickListenerForSettings() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
@@ -484,7 +396,7 @@
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
                 null, null, null,
                 false, true,
-                true, true,
+                true,
                 IMPORTANCE_DEFAULT, true);
         verify(mMetricsLogger).write(argThat(logMaker ->
                 logMaker.getCategory() == MetricsEvent.ACTION_NOTE_CONTROLS
@@ -499,7 +411,7 @@
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
                 null, null, null,
                 false, true,
-                true, true,
+                true,
                 IMPORTANCE_DEFAULT, true);
         mNotificationInfo.logBlockingHelperCounter("HowCanNotifsBeRealIfAppsArent");
         verify(mMetricsLogger).count(eq("HowCanNotifsBeRealIfAppsArent"), eq(1));
@@ -526,7 +438,7 @@
             throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null,
-                null, true, true, IMPORTANCE_DEFAULT, true);
+                null, true, false, IMPORTANCE_DEFAULT, true);
         final TextView channelNameView =
                 mNotificationInfo.findViewById(R.id.channel_name);
         assertEquals(GONE, channelNameView.getVisibility());
@@ -537,20 +449,24 @@
     public void testStopInvisibleIfBundleFromDifferentChannels() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, MULTIPLE_CHANNEL_COUNT, mSbn, null, null,
-                null, true, true, IMPORTANCE_DEFAULT, true);
-        final TextView blockView = mNotificationInfo.findViewById(R.id.block);
-        assertEquals(GONE, blockView.getVisibility());
+                null, true, false, IMPORTANCE_DEFAULT, true);
+        assertEquals(GONE, mNotificationInfo.findViewById(
+                R.id.interruptiveness_settings).getVisibility());
+        assertEquals(VISIBLE, mNotificationInfo.findViewById(
+                R.id.non_configurable_multichannel_text).getVisibility());
     }
 
     @Test
-    public void testbindNotification_UnblockableTextVisibleWhenAppUnblockable() throws Exception {
+    public void testBindNotification_whenAppUnblockable() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
                 IMPORTANCE_DEFAULT, true);
-        final TextView view = mNotificationInfo.findViewById(R.id.block_prompt);
+        final TextView view = mNotificationInfo.findViewById(R.id.non_configurable_text);
         assertEquals(View.VISIBLE, view.getVisibility());
         assertEquals(mContext.getString(R.string.notification_unblockable_desc),
                 view.getText());
+        assertEquals(GONE,
+                mNotificationInfo.findViewById(R.id.interruptiveness_settings).getVisibility());
     }
 
     @Test
@@ -568,23 +484,9 @@
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT, false);
+                IMPORTANCE_LOW, false);
 
-        mNotificationInfo.findViewById(R.id.int_block).performClick();
-        mTestableLooper.processAllMessages();
-        verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
-                anyString(), eq(TEST_UID), any());
-    }
-
-    @Test
-    public void testDoesNotUpdateNotificationChannelAfterImportanceChangedMin()
-            throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT, false);
-
-        mNotificationInfo.findViewById(R.id.minimize).performClick();
+        mNotificationInfo.findViewById(R.id.alert_row).performClick();
         mTestableLooper.processAllMessages();
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), eq(TEST_UID), any());
@@ -598,21 +500,7 @@
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
                 IMPORTANCE_DEFAULT, true);
 
-        mNotificationInfo.findViewById(R.id.int_silent).performClick();
-        mTestableLooper.processAllMessages();
-        verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
-                anyString(), eq(TEST_UID), any());
-    }
-
-    @Test
-    public void testDoesNotUpdateNotificationChannelAfterImportanceChangedUnSilenced()
-            throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT, false);
-
-        mNotificationInfo.findViewById(R.id.int_alert).performClick();
+        mNotificationInfo.findViewById(R.id.silent_row).performClick();
         mTestableLooper.processAllMessages();
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 anyString(), eq(TEST_UID), any());
@@ -650,76 +538,6 @@
     }
 
     @Test
-    public void testHandleCloseControls_setsNotificationsDisabledForMultipleChannelNotifications()
-            throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
-                10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */,
-                null /* onSettingsClick */, null /* onAppSettingsClick */,
-                true, false /* isNonblockable */, IMPORTANCE_DEFAULT, false
-        );
-
-        mNotificationInfo.findViewById(R.id.int_block_wrapper).performClick();
-        mNotificationInfo.findViewById(R.id.done_button).performClick();
-        mNotificationInfo.handleCloseControls(true, false);
-
-        mTestableLooper.processAllMessages();
-        verify(mMockINotificationManager, times(1))
-                .setNotificationsEnabledWithImportanceLockForPackage(
-                        anyString(), eq(TEST_UID), eq(false));
-    }
-
-
-    @Test
-    public void testHandleCloseControls_keepsNotificationsEnabledForMultipleChannelNotifications()
-            throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
-                10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */,
-                null /* onSettingsClick */, null /* onAppSettingsClick */,
-                true, false /* isNonblockable */, IMPORTANCE_DEFAULT, false
-        );
-
-        mNotificationInfo.findViewById(R.id.int_block_wrapper).performClick();
-        mNotificationInfo.findViewById(R.id.done_button).performClick();
-        mNotificationInfo.handleCloseControls(true, false);
-
-        mTestableLooper.processAllMessages();
-        verify(mMockINotificationManager, times(1))
-                .setNotificationsEnabledWithImportanceLockForPackage(
-                        anyString(), eq(TEST_UID), eq(false));
-    }
-
-    @Test
-    public void testCloseControls_blockingHelperSavesImportanceForMultipleChannelNotifications()
-            throws Exception {
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel /* notificationChannel */,
-                10 /* numUniqueChannelsInRow */, mSbn, null /* checkSaveListener */,
-                null /* onSettingsClick */, null /* onAppSettingsClick */,
-                true /* provisioned */,
-                false /* isNonblockable */, true /* isForBlockingHelper */,
-                true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true);
-
-        NotificationGuts guts = spy(new NotificationGuts(mContext, null));
-        when(guts.getWindowToken()).thenReturn(mock(IBinder.class));
-        doNothing().when(guts).animateClose(anyInt(), anyInt(), anyBoolean());
-        doNothing().when(guts).setExposed(anyBoolean(), anyBoolean());
-        guts.setGutsContent(mNotificationInfo);
-        mNotificationInfo.setGutsParent(guts);
-
-        mNotificationInfo.findViewById(R.id.done).performClick();
-
-        verify(mBlockingHelperManager).dismissCurrentBlockingHelper();
-        mTestableLooper.processAllMessages();
-        verify(mMockINotificationManager, times(1))
-                .setNotificationsEnabledWithImportanceLockForPackage(
-                        anyString(), eq(TEST_UID), eq(true));
-    }
-
-    @Test
     public void testCloseControls_nonNullCheckSaveListenerDoesntDelayKeepShowing_BlockingHelper()
             throws Exception {
         NotificationInfo.CheckSaveListener listener =
@@ -729,7 +547,7 @@
                 10 /* numUniqueChannelsInRow */, mSbn, listener /* checkSaveListener */,
                 null /* onSettingsClick */, null /* onAppSettingsClick */, true /* provisioned */,
                 false /* isNonblockable */, true /* isForBlockingHelper */,
-                true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true);
+                IMPORTANCE_DEFAULT, true);
 
         NotificationGuts guts = spy(new NotificationGuts(mContext, null));
         when(guts.getWindowToken()).thenReturn(mock(IBinder.class));
@@ -738,7 +556,7 @@
         guts.setGutsContent(mNotificationInfo);
         mNotificationInfo.setGutsParent(guts);
 
-        mNotificationInfo.findViewById(R.id.done).performClick();
+        mNotificationInfo.findViewById(R.id.keep_showing).performClick();
 
         verify(mBlockingHelperManager).dismissCurrentBlockingHelper();
         mTestableLooper.processAllMessages();
@@ -757,8 +575,7 @@
                 10 /* numUniqueChannelsInRow */, mSbn, listener /* checkSaveListener */,
                 null /* onSettingsClick */, null /* onAppSettingsClick */,
                 false /* isNonblockable */, true /* isForBlockingHelper */,
-                true, true /* isUserSentimentNegative */,  /* isNoisy */
-                IMPORTANCE_DEFAULT, true);
+                true, IMPORTANCE_DEFAULT, true);
 
         mNotificationInfo.handleCloseControls(true /* save */, false /* force */);
 
@@ -777,9 +594,9 @@
                 null /* onSettingsClick */, null /* onAppSettingsClick */,
                 true /* provisioned */,
                 false /* isNonblockable */, true /* isForBlockingHelper */,
-                true /* isUserSentimentNegative */, IMPORTANCE_DEFAULT, true);
+                IMPORTANCE_DEFAULT, true);
 
-        mNotificationInfo.findViewById(R.id.block).performClick();
+        mNotificationInfo.findViewById(R.id.deliver_silently).performClick();
         mTestableLooper.processAllMessages();
         verify(listener).checkSave(any(Runnable.class), eq(mSbn));
     }
@@ -799,7 +616,6 @@
                 false /* isNonblockable */,
                 true /* isForBlockingHelper */,
                 true,
-                false /* isUserSentimentNegative */,
                 IMPORTANCE_DEFAULT, true);
         NotificationGuts guts = mock(NotificationGuts.class);
         doCallRealMethod().when(guts).closeControls(anyInt(), anyInt(), anyBoolean(), anyBoolean());
@@ -811,129 +627,6 @@
     }
 
     @Test
-    public void testNonBlockableAppDoesNotBecomeBlocked() throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                true, false, IMPORTANCE_DEFAULT, false);
-        mNotificationInfo.findViewById(R.id.block).performClick();
-        waitForUndoButton();
-
-        mTestableLooper.processAllMessages();
-        verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
-                anyString(), eq(TEST_UID), any());
-    }
-
-    @Test
-    public void testBlockChangedCallsUpdateNotificationChannel_notBlockingHelper()
-            throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
-                null, null, null,
-                true, false,
-                IMPORTANCE_DEFAULT, false);
-
-        mNotificationInfo.findViewById(R.id.int_block_wrapper).performClick();
-        mNotificationInfo.findViewById(R.id.done_button).performClick();
-        mNotificationInfo.handleCloseControls(true, false);
-
-        ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
-        verify(mMetricsLogger, times(2)).write(logMakerCaptor.capture());
-        assertEquals(MetricsProto.MetricsEvent.TYPE_ACTION,
-                logMakerCaptor.getValue().getType());
-        assertEquals(IMPORTANCE_NONE - IMPORTANCE_LOW,
-                logMakerCaptor.getValue().getSubtype());
-
-        mTestableLooper.processAllMessages();
-        ArgumentCaptor<NotificationChannel> updated =
-                ArgumentCaptor.forClass(NotificationChannel.class);
-        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
-                anyString(), eq(TEST_UID), updated.capture());
-        assertTrue((updated.getValue().getUserLockedFields()
-                & USER_LOCKED_IMPORTANCE) != 0);
-        assertEquals(IMPORTANCE_NONE, updated.getValue().getImportance());
-    }
-
-    @Test
-    public void testBlockChangedCallsUpdateNotificationChannel_blockingHelper() throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(
-                mMockPackageManager,
-                mMockINotificationManager,
-                TEST_PACKAGE_NAME,
-                mNotificationChannel,
-                1 /* numChannels */,
-                mSbn,
-                null /* checkSaveListener */,
-                null /* onSettingsClick */,
-                null /* onAppSettingsClick */,
-                true /*provisioned */,
-                false /* isNonblockable */,
-                true /* isForBlockingHelper */,
-                true /* isUserSentimentNegative */,
-                IMPORTANCE_DEFAULT,
-                false);
-
-        mNotificationInfo.findViewById(R.id.block).performClick();
-        waitForUndoButton();
-        mNotificationInfo.handleCloseControls(true, false);
-
-        ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class);
-        verify(mMetricsLogger, times(3)).write(logMakerCaptor.capture());
-        assertEquals(MetricsProto.MetricsEvent.TYPE_ACTION,
-                logMakerCaptor.getValue().getType());
-        assertEquals(IMPORTANCE_NONE - IMPORTANCE_LOW,
-                logMakerCaptor.getValue().getSubtype());
-
-        mTestableLooper.processAllMessages();
-        ArgumentCaptor<NotificationChannel> updated =
-                ArgumentCaptor.forClass(NotificationChannel.class);
-        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
-                anyString(), eq(TEST_UID), updated.capture());
-        assertTrue((updated.getValue().getUserLockedFields()
-                & USER_LOCKED_IMPORTANCE) != 0);
-        assertEquals(IMPORTANCE_NONE, updated.getValue().getImportance());
-    }
-
-
-    @Test
-    public void testNonBlockableAppDoesNotBecomeMin() throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                true, false, IMPORTANCE_DEFAULT, false);
-        mNotificationInfo.findViewById(R.id.minimize).performClick();
-        waitForUndoButton();
-
-        mTestableLooper.processAllMessages();
-        verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
-                anyString(), eq(TEST_UID), any());
-    }
-
-    @Test
-    public void testMinChangedCallsUpdateNotificationChannel() throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                true, false, IMPORTANCE_DEFAULT, false);
-
-        mNotificationInfo.findViewById(R.id.minimize).performClick();
-        waitForUndoButton();
-        mNotificationInfo.handleCloseControls(true, false);
-
-        mTestableLooper.processAllMessages();
-        ArgumentCaptor<NotificationChannel> updated =
-                ArgumentCaptor.forClass(NotificationChannel.class);
-        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
-                anyString(), eq(TEST_UID), updated.capture());
-        assertTrue((updated.getValue().getUserLockedFields()
-                & USER_LOCKED_IMPORTANCE) != 0);
-        assertEquals(IMPORTANCE_MIN, updated.getValue().getImportance());
-    }
-
-    @Test
     public void testSilentlyChangedCallsUpdateNotificationChannel_blockingHelper()
             throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
@@ -950,7 +643,6 @@
                 true /*provisioned */,
                 false /* isNonblockable */,
                 true /* isForBlockingHelper */,
-                true /* isUserSentimentNegative */,
                 IMPORTANCE_DEFAULT,
                 false);
 
@@ -969,12 +661,13 @@
     }
 
     @Test
-    public void testKeepUpdatesNotificationChannel() throws Exception {
+    public void testKeepUpdatesNotificationChannel_blockingHelper() throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT, false);
+                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
+                IMPORTANCE_LOW, false);
 
+        mNotificationInfo.findViewById(R.id.keep_showing).performClick();
         mNotificationInfo.handleCloseControls(true, false);
 
         mTestableLooper.processAllMessages();
@@ -987,14 +680,32 @@
     }
 
     @Test
+    public void testNoActionsUpdatesNotificationChannel_blockingHelper() throws Exception {
+        mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
+                IMPORTANCE_DEFAULT, false);
+
+        mNotificationInfo.handleCloseControls(true, false);
+
+        mTestableLooper.processAllMessages();
+        ArgumentCaptor<NotificationChannel> updated =
+                ArgumentCaptor.forClass(NotificationChannel.class);
+        verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+                anyString(), eq(TEST_UID), updated.capture());
+        assertTrue(0 != (mNotificationChannel.getUserLockedFields() & USER_LOCKED_IMPORTANCE));
+        assertEquals(IMPORTANCE_DEFAULT, mNotificationChannel.getImportance());
+    }
+
+    @Test
     public void testSilenceCallsUpdateNotificationChannel() throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
                 IMPORTANCE_DEFAULT, true);
 
-        mNotificationInfo.findViewById(R.id.int_silent_wrapper).performClick();
-        mNotificationInfo.findViewById(R.id.done_button).performClick();
+        mNotificationInfo.findViewById(R.id.silent_row).performClick();
+        mNotificationInfo.findViewById(R.id.done).performClick();
         mNotificationInfo.handleCloseControls(true, false);
 
         mTestableLooper.processAllMessages();
@@ -1014,8 +725,8 @@
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
                 IMPORTANCE_DEFAULT, false);
 
-        mNotificationInfo.findViewById(R.id.int_alert_wrapper).performClick();
-        mNotificationInfo.findViewById(R.id.done_button).performClick();
+        mNotificationInfo.findViewById(R.id.alert_row).performClick();
+        mNotificationInfo.findViewById(R.id.done).performClick();
         mNotificationInfo.handleCloseControls(true, false);
 
         mTestableLooper.processAllMessages();
@@ -1036,8 +747,8 @@
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
                 IMPORTANCE_DEFAULT, true);
 
-        mNotificationInfo.findViewById(R.id.int_silent_wrapper).performClick();
-        mNotificationInfo.findViewById(R.id.done_button).performClick();
+        mNotificationInfo.findViewById(R.id.silent_row).performClick();
+        mNotificationInfo.findViewById(R.id.done).performClick();
         mNotificationInfo.handleCloseControls(true, false);
 
         mTestableLooper.processAllMessages();
@@ -1058,8 +769,8 @@
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
                 IMPORTANCE_LOW, false);
 
-        mNotificationInfo.findViewById(R.id.int_alert_wrapper).performClick();
-        mNotificationInfo.findViewById(R.id.done_button).performClick();
+        mNotificationInfo.findViewById(R.id.alert_row).performClick();
+        mNotificationInfo.findViewById(R.id.done).performClick();
         mNotificationInfo.handleCloseControls(true, false);
 
         mTestableLooper.processAllMessages();
@@ -1073,30 +784,14 @@
     }
 
     @Test
-    public void testCloseControlsDoesNotUpdateMinIfSaveIsFalse() throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                true, false, IMPORTANCE_DEFAULT, false);
-
-        mNotificationInfo.findViewById(R.id.minimize).performClick();
-        waitForUndoButton();
-        mNotificationInfo.handleCloseControls(false, false);
-
-        mTestableLooper.processAllMessages();
-        verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
-                eq(TEST_PACKAGE_NAME), eq(TEST_UID), eq(mNotificationChannel));
-    }
-
-    @Test
     public void testCloseControlsDoesNotUpdateIfSaveIsFalse() throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, false,
-                IMPORTANCE_DEFAULT, false);
+                IMPORTANCE_LOW, false);
 
-        mNotificationInfo.findViewById(R.id.int_block_wrapper).performClick();
-        mNotificationInfo.findViewById(R.id.done_button).performClick();
+        mNotificationInfo.findViewById(R.id.alert_row).performClick();
+        mNotificationInfo.findViewById(R.id.done).performClick();
         mNotificationInfo.handleCloseControls(false, false);
 
         mTestableLooper.processAllMessages();
@@ -1105,33 +800,17 @@
     }
 
     @Test
-    public void testBlockDoesNothingIfCheckSaveListenerIsNoOp() throws Exception {
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
-                (Runnable saveImportance, StatusBarNotification sbn) -> {
-                }, null, null, true, true, IMPORTANCE_DEFAULT, false);
-
-        mNotificationInfo.findViewById(R.id.int_block_wrapper).performClick();
-        mTestableLooper.processAllMessages();
-        mNotificationInfo.handleCloseControls(true, false);
-
-        verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
-                eq(TEST_PACKAGE_NAME), eq(TEST_UID), eq(mNotificationChannel));
-    }
-
-    @Test
     public void testCloseControlsUpdatesWhenCheckSaveListenerUsesCallback() throws Exception {
         mNotificationChannel.setImportance(IMPORTANCE_LOW);
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn,
                 (Runnable saveImportance, StatusBarNotification sbn) -> {
                     saveImportance.run();
-                }, null, null, true, false, IMPORTANCE_DEFAULT, false
+                }, null, null, true, false, IMPORTANCE_LOW, false
         );
 
-        mNotificationInfo.findViewById(R.id.int_block_wrapper).performClick();
-        mNotificationInfo.findViewById(R.id.done_button).performClick();
+        mNotificationInfo.findViewById(R.id.alert_row).performClick();
+        mNotificationInfo.findViewById(R.id.done).performClick();
         mTestableLooper.processAllMessages();
         verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
                 eq(TEST_PACKAGE_NAME), eq(TEST_UID), eq(mNotificationChannel));
@@ -1147,18 +826,4 @@
     public void testWillBeRemovedReturnsFalseBeforeBind() throws Exception {
         assertFalse(mNotificationInfo.willBeRemoved());
     }
-
-    @Test
-    public void testUndoText_min() throws Exception {
-        mSbn.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
-        mNotificationChannel.setImportance(IMPORTANCE_LOW);
-        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
-                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null, null, true, true,
-                true, false, IMPORTANCE_DEFAULT, false);
-
-        mNotificationInfo.findViewById(R.id.minimize).performClick();
-        waitForUndoButton();
-        TextView confirmationText = mNotificationInfo.findViewById(R.id.confirmation_text);
-        assertTrue(confirmationText.getText().toString().contains("minimized"));
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 1248cbb..67ad37b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -45,7 +45,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Collections;
-import java.util.Set;
+import java.util.List;
 
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
@@ -118,10 +118,10 @@
         verify(mQsTileHost, never()).addTile("night");
     }
 
-    private static Set<CastDevice> buildFakeCastDevice(boolean isCasting) {
+    private static List<CastDevice> buildFakeCastDevice(boolean isCasting) {
         CastDevice cd = new CastDevice();
         cd.state = isCasting ? CastDevice.STATE_CONNECTED : CastDevice.STATE_DISCONNECTED;
-        return Collections.singleton(cd);
+        return Collections.singletonList(cd);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 3b56e45..6d6af47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.verify;
@@ -23,6 +25,8 @@
 
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
 
 import androidx.test.filters.SmallTest;
 
@@ -32,6 +36,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.AmbientPulseManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -54,6 +59,8 @@
 public class NotificationPanelViewTest extends SysuiTestCase {
 
     @Mock
+    private StatusBar mStatusBar;
+    @Mock
     private SysuiStatusBarStateController mStatusBarStateController;
     @Mock
     private NotificationStackScrollLayout mNotificationStackScrollLayout;
@@ -62,12 +69,33 @@
     @Mock
     private KeyguardBottomAreaView mKeyguardBottomArea;
     @Mock
+    private KeyguardBottomAreaView mQsFrame;
+    @Mock
+    private ViewGroup mBigClockContainer;
+    @Mock
+    private ScrimController mScrimController;
+    @Mock
+    private NotificationIconAreaController mNotificationAreaController;
+    @Mock
+    private HeadsUpManagerPhone mHeadsUpManager;
+    @Mock
+    private NotificationShelf mNotificationShelf;
+    @Mock
+    private NotificationGroupManager mGroupManager;
+    @Mock
     private KeyguardStatusBarView mKeyguardStatusBar;
+    @Mock
+    private HeadsUpTouchHelper.Callback mHeadsUpCallback;
+    @Mock
+    private PanelBar mPanelBar;
     private NotificationPanelView mNotificationPanelView;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
+        when(mNotificationStackScrollLayout.getHeight()).thenReturn(1000);
+        when(mNotificationStackScrollLayout.getHeadsUpCallback()).thenReturn(mHeadsUpCallback);
+        when(mHeadsUpCallback.getContext()).thenReturn(mContext);
         mDependency.injectTestDependency(StatusBarStateController.class,
                 mStatusBarStateController);
         mDependency.injectMockDependency(ShadeController.class);
@@ -80,6 +108,8 @@
                         new StatusBarStateControllerImpl());
         PulseExpansionHandler expansionHandler = new PulseExpansionHandler(mContext, coordinator);
         mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler);
+        mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
+        mNotificationPanelView.setBar(mPanelBar);
     }
 
     @Test
@@ -105,6 +135,37 @@
         verify(mNotificationStackScrollLayout).setShowDarkShelf(eq(false));
     }
 
+    @Test
+    public void testSetExpandedHeight() {
+        mNotificationPanelView.setExpandedHeight(200);
+        assertThat((int) mNotificationPanelView.getExpandedHeight()).isEqualTo(200);
+    }
+
+    @Test
+    public void testOnTouchEvent_expansionCanBeBlocked() {
+        mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+                0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
+                0 /* metaState */));
+        mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+                0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
+                0 /* metaState */));
+        assertThat((int) mNotificationPanelView.getExpandedHeight()).isEqualTo(200);
+        assertThat(mNotificationPanelView.isTrackingBlocked()).isFalse();
+
+        mNotificationPanelView.blockExpansionForCurrentTouch();
+        mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+                0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */,
+                0 /* metaState */));
+        // Expansion should not have changed because it was blocked
+        assertThat((int) mNotificationPanelView.getExpandedHeight()).isEqualTo(200);
+        assertThat(mNotificationPanelView.isTrackingBlocked()).isTrue();
+
+        mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+                0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
+                0 /* metaState */));
+        assertThat(mNotificationPanelView.isTrackingBlocked()).isFalse();
+    }
+
     private class TestableNotificationPanelView extends NotificationPanelView {
         TestableNotificationPanelView(NotificationWakeUpCoordinator coordinator,
                 PulseExpansionHandler expansionHandler) {
@@ -116,6 +177,14 @@
             mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView;
             mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar;
             mKeyguardBottomArea = NotificationPanelViewTest.this.mKeyguardBottomArea;
+            mBigClockContainer = NotificationPanelViewTest.this.mBigClockContainer;
+            mQsFrame = NotificationPanelViewTest.this.mQsFrame;
+            initDependencies(NotificationPanelViewTest.this.mStatusBar,
+                    NotificationPanelViewTest.this.mGroupManager,
+                    NotificationPanelViewTest.this.mNotificationShelf,
+                    NotificationPanelViewTest.this.mHeadsUpManager,
+                    NotificationPanelViewTest.this.mNotificationAreaController,
+                    NotificationPanelViewTest.this.mScrimController);
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java
deleted file mode 100644
index 7829830..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java
+++ /dev/null
@@ -1,696 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
-
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_DEAD_ZONE;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_HOME;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
-import static com.android.systemui.statusbar.phone.NavigationBarView.WINDOW_TARGET_BOTTOM;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.anyFloat;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
-import android.view.MotionEvent;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.recents.OverviewProxyService;
-import com.android.systemui.shared.recents.IOverviewProxy;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.MockitoAnnotations;
-
-/** atest QuickStepControllerTest */
-@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
-@SmallTest
-public class QuickStepControllerTest extends SysuiTestCase {
-    private static final int NAVBAR_WIDTH = 1000;
-    private static final int NAVBAR_HEIGHT = 300;
-
-    private QuickStepController mController;
-    private NavigationBarView mNavigationBarView;
-    private StatusBar mStatusBar;
-    private OverviewProxyService mProxyService;
-    private IOverviewProxy mProxy;
-    private Resources mResources;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        final ButtonDispatcher backButton = mock(ButtonDispatcher.class);
-        mResources = mock(Resources.class);
-
-        mProxyService = mock(OverviewProxyService.class);
-        mProxy = mock(IOverviewProxy.Stub.class);
-        doReturn(mProxy).when(mProxyService).getProxy();
-        doReturn(true).when(mProxyService).shouldShowSwipeUpUI();
-        mDependency.injectTestDependency(OverviewProxyService.class, mProxyService);
-
-        mStatusBar = mock(StatusBar.class);
-        doReturn(false).when(mStatusBar).isKeyguardShowing();
-        mContext.putComponent(StatusBar.class, mStatusBar);
-
-        mNavigationBarView = mock(NavigationBarView.class);
-        doReturn(false).when(mNavigationBarView).inScreenPinning();
-        doReturn(true).when(mNavigationBarView).isNotificationsFullyCollapsed();
-        doReturn(true).when(mNavigationBarView).isQuickScrubEnabled();
-        doReturn(HIT_TARGET_NONE).when(mNavigationBarView).getDownHitTarget();
-        doReturn(WINDOW_TARGET_BOTTOM).when(mNavigationBarView).getWindowTarget();
-        doReturn(backButton).when(mNavigationBarView).getBackButton();
-        doReturn(mResources).when(mNavigationBarView).getResources();
-        doReturn(mContext).when(mNavigationBarView).getContext();
-
-        mController = new QuickStepController(mContext);
-        mController.setComponents(mNavigationBarView);
-        mController.setBarState(false /* isRTL */, NAV_BAR_BOTTOM);
-    }
-
-    @Test
-    public void testNoActionsNoGestures() throws Exception {
-        MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1);
-        assertFalse(mController.onInterceptTouchEvent(ev));
-        verify(mNavigationBarView, never()).requestUnbufferedDispatch(ev);
-        assertNull(mController.getCurrentAction());
-    }
-
-    @Test
-    public void testNoGesturesWhenSwipeUpDisabled() throws Exception {
-        doReturn(false).when(mProxyService).shouldShowSwipeUpUI();
-        mController.setGestureActions(mockAction(true), null /* swipeDownAction */,
-                null /* swipeLeftAction */, null /* swipeRightAction */,  null /* leftEdgeSwipe */,
-                null /* rightEdgeSwipe */);
-
-        MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1);
-        assertFalse(mController.onInterceptTouchEvent(ev));
-        verify(mNavigationBarView, never()).requestUnbufferedDispatch(ev);
-        assertNull(mController.getCurrentAction());
-    }
-
-    @Test
-    public void testHasActionDetectGesturesTouchdown() throws Exception {
-        MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1);
-
-        // Add enabled gesture action
-        NavigationGestureAction action = mockAction(true);
-        mController.setGestureActions(action, null /* swipeDownAction */,
-                null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */,
-                null /* rightEdgeSwipe */);
-
-        assertFalse(mController.onInterceptTouchEvent(ev));
-        verify(mNavigationBarView, times(1)).requestUnbufferedDispatch(ev);
-        verify(action, times(1)).reset();
-        verify(mProxy, times(1)).onPreMotionEvent(mNavigationBarView.getDownHitTarget());
-        verify(mProxy, times(1)).onMotionEvent(ev);
-        assertNull(mController.getCurrentAction());
-    }
-
-    @Test
-    public void testProxyDisconnectedNoGestures() throws Exception {
-        MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1);
-
-        // Add enabled gesture action
-        mController.setGestureActions(mockAction(true), null /* swipeDownAction */,
-                null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */,
-                null /* rightEdgeSwipe */);
-
-        // Set the gesture on deadzone
-        doReturn(null).when(mProxyService).getProxy();
-
-        assertFalse(mController.onInterceptTouchEvent(ev));
-        verify(mNavigationBarView, never()).requestUnbufferedDispatch(ev);
-        assertNull(mController.getCurrentAction());
-    }
-
-    @Test
-    public void testNoActionsNoGesturesOverDeadzone() throws Exception {
-        MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1);
-
-        // Touched over deadzone
-        doReturn(HIT_TARGET_DEAD_ZONE).when(mNavigationBarView).getDownHitTarget();
-
-        assertTrue(mController.onInterceptTouchEvent(ev));
-        verify(mNavigationBarView, never()).requestUnbufferedDispatch(ev);
-        assertNull(mController.getCurrentAction());
-    }
-
-    @Test
-    public void testOnTouchIgnoredDownEventAfterOnIntercept() {
-        mController.setGestureActions(mockAction(true), null /* swipeDownAction */,
-                null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */,
-                null /* rightEdgeSwipe */);
-
-        MotionEvent ev = event(MotionEvent.ACTION_DOWN, 1, 1);
-        assertFalse(touch(ev));
-        verify(mNavigationBarView, times(1)).requestUnbufferedDispatch(ev);
-
-        // OnTouch event for down is ignored, so requestUnbufferedDispatch ran once from before
-        assertFalse(mNavigationBarView.onTouchEvent(ev));
-        verify(mNavigationBarView, times(1)).requestUnbufferedDispatch(ev);
-    }
-
-    @Test
-    public void testGesturesCallCorrectAction() throws Exception {
-        doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth();
-        doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight();
-
-        NavigationGestureAction swipeUp = mockAction(true);
-        NavigationGestureAction swipeDown = mockAction(true);
-        NavigationGestureAction swipeLeft = mockAction(true);
-        NavigationGestureAction swipeRight = mockAction(true);
-        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight,
-                null /* leftEdgeSwipe */, null /* rightEdgeSwipe */);
-
-        // Swipe Up
-        assertGestureTriggersAction(swipeUp, 1, 100, 5, 1);
-        // Swipe Down
-        assertGestureTriggersAction(swipeDown, 1, 1, 5, 100);
-        // Swipe Left
-        assertGestureTriggersAction(swipeLeft, NAVBAR_WIDTH / 2, 1, 5, 1);
-        // Swipe Right
-        assertGestureTriggersAction(swipeRight, NAVBAR_WIDTH / 2, 1, NAVBAR_WIDTH, 5);
-    }
-
-    @Test
-    public void testGesturesCallCorrectActionLandscape() throws Exception {
-        doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getWidth();
-        doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getHeight();
-
-        NavigationGestureAction swipeUp = mockAction(true);
-        NavigationGestureAction swipeDown = mockAction(true);
-        NavigationGestureAction swipeLeft = mockAction(true);
-        NavigationGestureAction swipeRight = mockAction(true);
-        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight,
-                null /* leftEdgeSwipe */, null /* rightEdgeSwipe */);
-
-        // In landscape
-        mController.setBarState(false /* isRTL */, NAV_BAR_RIGHT);
-
-        // Swipe Up
-        assertGestureTriggersAction(swipeRight, 1, 100, 5, 1);
-        // Swipe Down
-        assertGestureTriggersAction(swipeLeft, 1, NAVBAR_WIDTH / 2, 5, NAVBAR_WIDTH);
-        // Swipe Left
-        assertGestureTriggersAction(swipeUp, 100, 1, 5, 1);
-        // Swipe Right
-        assertGestureTriggersAction(swipeDown, 1, 1, 100, 5);
-    }
-
-    @Test
-    public void testGesturesCallCorrectActionSeascape() throws Exception {
-        doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getWidth();
-        doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getHeight();
-
-        mController.setBarState(false /* isRTL */, NAV_BAR_LEFT);
-        NavigationGestureAction swipeUp = mockAction(true);
-        NavigationGestureAction swipeDown = mockAction(true);
-        NavigationGestureAction swipeLeft = mockAction(true);
-        NavigationGestureAction swipeRight = mockAction(true);
-        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight,
-                null /* leftEdgeSwipe */, null /* rightEdgeSwipe */);
-
-        // Swipe Up
-        assertGestureTriggersAction(swipeLeft, 1, NAVBAR_WIDTH / 2, 5, 1);
-        // Swipe Down
-        assertGestureTriggersAction(swipeRight, 1, NAVBAR_WIDTH / 2, 5, NAVBAR_WIDTH);
-        // Swipe Left
-        assertGestureTriggersAction(swipeDown, 100, 1, 5, 1);
-        // Swipe Right
-        assertGestureTriggersAction(swipeUp, 1, 1, 100, 5);
-    }
-
-    @Test
-    public void testGesturesCallCorrectActionRTL() throws Exception {
-        doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth();
-        doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight();
-        mController.setBarState(true /* isRTL */, NAV_BAR_BOTTOM);
-
-        // The swipe gestures below are for LTR, so RTL in portrait will be swapped
-        NavigationGestureAction swipeUp = mockAction(true);
-        NavigationGestureAction swipeDown = mockAction(true);
-        NavigationGestureAction swipeLeft = mockAction(true);
-        NavigationGestureAction swipeRight = mockAction(true);
-        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight,
-                null /* leftEdgeSwipe */, null /* rightEdgeSwipe */);
-
-        // Swipe Up in RTL
-        assertGestureTriggersAction(swipeUp, 1, 100, 5, 1);
-        // Swipe Down in RTL
-        assertGestureTriggersAction(swipeDown, 1, 1, 5, 100);
-        // Swipe Left in RTL
-        assertGestureTriggersAction(swipeRight, NAVBAR_WIDTH / 2, 1, 5, 1);
-        // Swipe Right in RTL
-        assertGestureTriggersAction(swipeLeft, NAVBAR_WIDTH / 2, 1, NAVBAR_WIDTH, 0);
-    }
-
-    @Test
-    public void testGesturesCallCorrectActionLandscapeRTL() throws Exception {
-        doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getWidth();
-        doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getHeight();
-        mController.setBarState(true /* isRTL */, NAV_BAR_RIGHT);
-
-        // The swipe gestures below are for LTR, so RTL in landscape will be swapped
-        NavigationGestureAction swipeUp = mockAction(true);
-        NavigationGestureAction swipeDown = mockAction(true);
-        NavigationGestureAction swipeLeft = mockAction(true);
-        NavigationGestureAction swipeRight = mockAction(true);
-        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight,
-                null /* leftEdgeSwipe */, null /* rightEdgeSwipe */);
-
-        // Swipe Up
-        assertGestureTriggersAction(swipeLeft, 1, NAVBAR_WIDTH / 2, 5, 1);
-        // Swipe Down
-        assertGestureTriggersAction(swipeRight, 1, NAVBAR_WIDTH / 2, 5, NAVBAR_WIDTH);
-        // Swipe Left
-        assertGestureTriggersAction(swipeUp, 100, 1, 5, 1);
-        // Swipe Right
-        assertGestureTriggersAction(swipeDown, 1, 1, 100, 5);
-    }
-
-    @Test
-    public void testGesturesCallCorrectActionSeascapeRTL() throws Exception {
-        doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getWidth();
-        doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getHeight();
-        mController.setBarState(true /* isRTL */, NAV_BAR_LEFT);
-
-        // The swipe gestures below are for LTR, so RTL in seascape will be swapped
-        NavigationGestureAction swipeUp = mockAction(true);
-        NavigationGestureAction swipeDown = mockAction(true);
-        NavigationGestureAction swipeLeft = mockAction(true);
-        NavigationGestureAction swipeRight = mockAction(true);
-        mController.setGestureActions(swipeUp, swipeDown, swipeLeft, swipeRight,
-                null /* leftEdgeSwipe */, null /* rightEdgeSwipe */);
-
-        // Swipe Up
-        assertGestureTriggersAction(swipeRight, 1, NAVBAR_WIDTH / 2, 5, 1);
-        // Swipe Down
-        assertGestureTriggersAction(swipeLeft, 1, NAVBAR_WIDTH / 2, 5, NAVBAR_WIDTH);
-        // Swipe Left
-        assertGestureTriggersAction(swipeDown, 100, 1, 5, 1);
-        // Swipe Right
-        assertGestureTriggersAction(swipeUp, 1, 1, 100, 5);
-    }
-
-    @Test
-    public void testActionPreventByPinnedState() throws Exception {
-        // Screen is pinned
-        doReturn(true).when(mNavigationBarView).inScreenPinning();
-
-        // Add enabled gesture action
-        NavigationGestureAction action = mockAction(true);
-        mController.setGestureActions(action, null /* swipeDownAction */,
-                null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */,
-                null /* rightEdgeSwipe */);
-
-        // Touch down to begin swipe
-        MotionEvent downEvent = event(MotionEvent.ACTION_DOWN, 1, 100);
-        assertFalse(touch(downEvent));
-        verify(mProxy, never()).onPreMotionEvent(mNavigationBarView.getDownHitTarget());
-        verify(mProxy, never()).onMotionEvent(downEvent);
-
-        // Move to start gesture, but pinned so it should not trigger action
-        MotionEvent moveEvent = event(MotionEvent.ACTION_MOVE, 1, 1);
-        assertFalse(touch(moveEvent));
-        assertNull(mController.getCurrentAction());
-        verify(mNavigationBarView, times(1)).showPinningEscapeToast();
-        verify(action, never()).onGestureStart(moveEvent);
-    }
-
-    @Test
-    public void testActionPreventedNotificationsShown() throws Exception {
-        NavigationGestureAction action = mockAction(true);
-        doReturn(false).when(action).canRunWhenNotificationsShowing();
-        mController.setGestureActions(action, null /* swipeDownAction */,
-                null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */,
-                null /* rightEdgeSwipe */);
-
-        // Show the notifications
-        doReturn(false).when(mNavigationBarView).isNotificationsFullyCollapsed();
-
-        // Swipe up
-        assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100));
-        assertFalse(touch(MotionEvent.ACTION_MOVE, 1, 1));
-        assertNull(mController.getCurrentAction());
-        assertFalse(touch(MotionEvent.ACTION_UP, 1, 1));
-
-        // Hide the notifications
-        doReturn(true).when(mNavigationBarView).isNotificationsFullyCollapsed();
-
-        // Swipe up
-        assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100));
-        assertTrue(touch(MotionEvent.ACTION_MOVE, 1, 1));
-        assertEquals(action, mController.getCurrentAction());
-        assertFalse(touch(MotionEvent.ACTION_UP, 1, 1));
-    }
-
-    @Test
-    public void testActionCannotPerform() throws Exception {
-        NavigationGestureAction action = mockAction(true);
-        mController.setGestureActions(action, null /* swipeDownAction */,
-                null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */,
-                null /* rightEdgeSwipe */);
-
-        // Cannot perform action
-        doReturn(false).when(action).canPerformAction();
-
-        // Swipe up
-        assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100));
-        assertFalse(touch(MotionEvent.ACTION_MOVE, 1, 1));
-        assertNull(mController.getCurrentAction());
-        assertFalse(touch(MotionEvent.ACTION_UP, 1, 1));
-
-        // Cannot perform action
-        doReturn(true).when(action).canPerformAction();
-
-        // Swipe up
-        assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100));
-        assertTrue(touch(MotionEvent.ACTION_MOVE, 1, 1));
-        assertEquals(action, mController.getCurrentAction());
-        assertFalse(touch(MotionEvent.ACTION_UP, 1, 1));
-    }
-
-    @Test
-    public void testQuickScrub() throws Exception {
-        doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth();
-        doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight();
-        QuickScrubAction action = spy(new QuickScrubAction(mNavigationBarView, mProxyService));
-        mController.setGestureActions(null /* swipeUpAction */, null /* swipeDownAction */,
-                null /* swipeLeftAction */, action, null /* leftEdgeSwipe */,
-                null /* rightEdgeSwipe */);
-        int x = NAVBAR_WIDTH / 2;
-        int y = 20;
-
-        // Set the layout and other padding to make sure the scrub fraction is calculated correctly
-        action.onLayout(true, 0, 0, NAVBAR_WIDTH, NAVBAR_HEIGHT);
-        doReturn(0).when(mNavigationBarView).getPaddingLeft();
-        doReturn(0).when(mNavigationBarView).getPaddingRight();
-        doReturn(0).when(mNavigationBarView).getPaddingStart();
-        doReturn(0).when(mResources)
-                .getDimensionPixelSize(R.dimen.nav_quick_scrub_track_edge_padding);
-
-        // Quickscrub disabled, so the action should be disabled
-        doReturn(false).when(mNavigationBarView).isQuickScrubEnabled();
-        assertFalse(action.isEnabled());
-        doReturn(true).when(mNavigationBarView).isQuickScrubEnabled();
-
-        // Touch down
-        MotionEvent downEvent = event(MotionEvent.ACTION_DOWN, x, y);
-        assertFalse(touch(downEvent));
-        assertNull(mController.getCurrentAction());
-        verify(mProxy, times(1)).onPreMotionEvent(mNavigationBarView.getDownHitTarget());
-        verify(mProxy, times(1)).onMotionEvent(downEvent);
-
-        // Move to start trigger action from gesture
-        MotionEvent moveEvent1 = event(MotionEvent.ACTION_MOVE, x + 100, y);
-        assertTrue(touch(moveEvent1));
-        assertEquals(action, mController.getCurrentAction());
-        verify(action, times(1)).onGestureStart(moveEvent1);
-        verify(mProxy, times(1)).onQuickScrubStart();
-        verify(mProxyService, times(1)).notifyQuickScrubStarted();
-        verify(mNavigationBarView, times(1)).updateSlippery();
-        verify(mProxy, never()).onMotionEvent(moveEvent1);
-
-        // Move again for scrub
-        float fraction = 3f / 4;
-        x = (int) (NAVBAR_WIDTH * fraction);
-        MotionEvent moveEvent2 = event(MotionEvent.ACTION_MOVE, x, y);
-        assertTrue(touch(moveEvent2));
-        assertEquals(action, mController.getCurrentAction());
-        verify(action, times(1)).onGestureMove(x, y);
-        verify(mProxy, times(1)).onQuickScrubProgress(fraction);
-        verify(mProxy, never()).onMotionEvent(moveEvent2);
-
-        // Action up
-        MotionEvent upEvent = event(MotionEvent.ACTION_UP, 1, y);
-        assertFalse(touch(upEvent));
-        assertNull(mController.getCurrentAction());
-        verify(action, times(1)).onGestureEnd();
-        verify(mProxy, times(1)).onQuickScrubEnd();
-        verify(mProxy, never()).onMotionEvent(upEvent);
-    }
-
-    @Test
-    public void testQuickStep() throws Exception {
-        QuickStepAction action = new QuickStepAction(mNavigationBarView, mProxyService);
-        mController.setGestureActions(action, null /* swipeDownAction */,
-                null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */,
-                null /* rightEdgeSwipe */);
-
-        // Notifications are up, should prevent quickstep
-        doReturn(false).when(mNavigationBarView).isNotificationsFullyCollapsed();
-
-        // Swipe up
-        assertFalse(touch(MotionEvent.ACTION_DOWN, 1, 100));
-        assertNull(mController.getCurrentAction());
-        assertFalse(touch(MotionEvent.ACTION_MOVE, 1, 1));
-        assertNull(mController.getCurrentAction());
-        assertFalse(touch(MotionEvent.ACTION_UP, 1, 1));
-        doReturn(true).when(mNavigationBarView).isNotificationsFullyCollapsed();
-
-        // Quickstep disabled, so the action should be disabled
-        doReturn(false).when(mNavigationBarView).isQuickStepSwipeUpEnabled();
-        assertFalse(action.isEnabled());
-        doReturn(true).when(mNavigationBarView).isQuickStepSwipeUpEnabled();
-
-        // Swipe up should call proxy events
-        MotionEvent downEvent = event(MotionEvent.ACTION_DOWN, 1, 100);
-        assertFalse(touch(downEvent));
-        assertNull(mController.getCurrentAction());
-        verify(mProxy, times(1)).onPreMotionEvent(mNavigationBarView.getDownHitTarget());
-        verify(mProxy, times(1)).onMotionEvent(downEvent);
-
-        MotionEvent moveEvent = event(MotionEvent.ACTION_MOVE, 1, 1);
-        assertTrue(touch(moveEvent));
-        assertEquals(action, mController.getCurrentAction());
-        verify(mProxy, times(1)).onQuickStep(moveEvent);
-        verify(mProxyService, times(1)).notifyQuickStepStarted();
-    }
-
-    @Test
-    public void testLongPressPreventDetection() throws Exception {
-        NavigationGestureAction action = mockAction(true);
-        mController.setGestureActions(action, null /* swipeDownAction */,
-                null /* swipeLeftAction */, null /* swipeRightAction */, null /* leftEdgeSwipe */,
-                null /* rightEdgeSwipe */);
-
-        // Start the drag up
-        assertFalse(touch(MotionEvent.ACTION_DOWN, 100, 1));
-        assertNull(mController.getCurrentAction());
-
-        // Long press something on the navigation bar such as Home button
-        mNavigationBarView.onNavigationButtonLongPress(mock(View.class));
-
-        // Swipe right will not start any gestures
-        MotionEvent motionMoveEvent = event(MotionEvent.ACTION_MOVE, 1, 1);
-        assertFalse(touch(motionMoveEvent));
-        assertNull(mController.getCurrentAction());
-        verify(action, never()).startGesture(motionMoveEvent);
-
-        // Touch up
-        assertFalse(touch(MotionEvent.ACTION_UP, 1, 1));
-        verify(action, never()).endGesture();
-    }
-
-    @Test
-    public void testHitTargetDragged() throws Exception {
-        ButtonDispatcher button = mock(ButtonDispatcher.class);
-        FakeLocationView buttonView = spy(new FakeLocationView(mContext, NAVBAR_WIDTH / 2,
-                NAVBAR_HEIGHT / 2));
-        doReturn(buttonView).when(button).getCurrentView();
-
-        NavigationGestureAction action = mockAction(true);
-        mController.setGestureActions(action, action, action, action, action, action);
-
-        // Setup getting the hit target
-        doReturn(HIT_TARGET_HOME).when(action).requiresTouchDownHitTarget();
-        doReturn(true).when(action).allowHitTargetToMoveOverDrag();
-        doReturn(HIT_TARGET_HOME).when(mNavigationBarView).getDownHitTarget();
-        doReturn(button).when(mNavigationBarView).getHomeButton();
-        doReturn(NAVBAR_WIDTH).when(mNavigationBarView).getWidth();
-        doReturn(NAVBAR_HEIGHT).when(mNavigationBarView).getHeight();
-
-        // Portrait
-        assertGestureDragsHitTargetAllDirections(buttonView, false /* isRTL */, NAV_BAR_BOTTOM);
-
-        // Portrait RTL
-        assertGestureDragsHitTargetAllDirections(buttonView, true /* isRTL */, NAV_BAR_BOTTOM);
-
-        // Landscape
-        assertGestureDragsHitTargetAllDirections(buttonView, false /* isRTL */, NAV_BAR_RIGHT);
-
-        // Landscape RTL
-        assertGestureDragsHitTargetAllDirections(buttonView, true /* isRTL */, NAV_BAR_RIGHT);
-
-        // Seascape
-        assertGestureDragsHitTargetAllDirections(buttonView, false /* isRTL */, NAV_BAR_LEFT);
-
-        // Seascape RTL
-        assertGestureDragsHitTargetAllDirections(buttonView, true /* isRTL */, NAV_BAR_LEFT);
-    }
-
-    private void assertGestureDragsHitTargetAllDirections(View buttonView, boolean isRTL,
-            int navPos) {
-        mController.setBarState(isRTL, navPos);
-
-        // Swipe up
-        assertGestureDragsHitTarget(buttonView, 10 /* x1 */, 200 /* y1 */, 0 /* x2 */, 0 /* y2 */,
-                0 /* dx */, -1 /* dy */);
-        // Swipe left
-        assertGestureDragsHitTarget(buttonView, 200 /* x1 */, 10 /* y1 */, 0 /* x2 */, 0 /* y2 */,
-                -1 /* dx */, 0 /* dy */);
-        // Swipe right
-        assertGestureDragsHitTarget(buttonView, 0 /* x1 */, 0 /* y1 */, 200 /* x2 */, 10 /* y2 */,
-                1 /* dx */, 0 /* dy */);
-        // Swipe down
-        assertGestureDragsHitTarget(buttonView, 0 /* x1 */, 0 /* y1 */, 10 /* x2 */, 200 /* y2 */,
-                0 /* dx */, 1 /* dy */);
-    }
-
-    /**
-     * Asserts the gesture actually moves the hit target
-     * @param buttonView button to check if moved, use Mockito.spy on a real object
-     * @param x1 start x
-     * @param x2 start y
-     * @param y1 end x
-     * @param y2 end y
-     * @param dx diff in x, if not 0, its sign determines direction, value does not matter
-     * @param dy diff in y, if not 0, its sign determines direction, value does not matter
-     */
-    private void assertGestureDragsHitTarget(View buttonView, int x1, int y1, int x2, int y2,
-            int dx, int dy) {
-        ArgumentCaptor<Float> captor = ArgumentCaptor.forClass(Float.class);
-        assertFalse(touch(MotionEvent.ACTION_DOWN, x1, y1));
-        assertTrue(touch(MotionEvent.ACTION_MOVE, x2, y2));
-
-        // Verify positions of the button drag
-        if (dx == 0) {
-            verify(buttonView, never()).setTranslationX(anyFloat());
-        } else {
-            verify(buttonView).setTranslationX(captor.capture());
-            if (dx < 0) {
-                assertTrue("Button should have moved left", (float) captor.getValue() < 0);
-            } else {
-                assertTrue("Button should have moved right", (float) captor.getValue() > 0);
-            }
-        }
-        if (dy == 0) {
-            verify(buttonView, never()).setTranslationY(anyFloat());
-        } else {
-            verify(buttonView).setTranslationY(captor.capture());
-            if (dy < 0) {
-                assertTrue("Button should have moved up", (float) captor.getValue() < 0);
-            } else {
-                assertTrue("Button should have moved down", (float) captor.getValue() > 0);
-            }
-        }
-
-        // Touch up
-        assertFalse(touch(MotionEvent.ACTION_UP, x2, y2));
-        verify(buttonView, times(1)).animate();
-
-        // Reset button state
-        reset(buttonView);
-    }
-
-    private MotionEvent event(int action, float x, float y) {
-        final MotionEvent event = mock(MotionEvent.class);
-        doReturn(x).when(event).getX();
-        doReturn(y).when(event).getY();
-        doReturn(action & MotionEvent.ACTION_MASK).when(event).getActionMasked();
-        doReturn(action).when(event).getAction();
-        return event;
-    }
-
-    private boolean touch(int action, float x, float y) {
-        return touch(event(action, x, y));
-    }
-
-    private boolean touch(MotionEvent event) {
-        return mController.onInterceptTouchEvent(event);
-    }
-
-    private NavigationGestureAction mockAction(boolean enabled) {
-        final NavigationGestureAction action = mock(NavigationGestureAction.class);
-        doReturn(enabled).when(action).isEnabled();
-        doReturn(HIT_TARGET_NONE).when(action).requiresTouchDownHitTarget();
-        doReturn(true).when(action).canPerformAction();
-        return action;
-    }
-
-    private void assertGestureTriggersAction(NavigationGestureAction action, int x1, int y1,
-            int x2, int y2) {
-        // Start the drag
-        assertFalse(touch(MotionEvent.ACTION_DOWN, x1, y1));
-        assertNull(mController.getCurrentAction());
-
-        // Swipe
-        MotionEvent motionMoveEvent = event(MotionEvent.ACTION_MOVE, x2, y2);
-        assertTrue(touch(motionMoveEvent));
-        assertEquals(action, mController.getCurrentAction());
-        verify(action, times(1)).startGesture(motionMoveEvent);
-
-        // Move again
-        assertTrue(touch(MotionEvent.ACTION_MOVE, x2, y2));
-        verify(action, times(1)).onGestureMove(x2, y2);
-
-        // Touch up
-        assertFalse(touch(MotionEvent.ACTION_UP, x2, y2));
-        assertNull(mController.getCurrentAction());
-        verify(action, times(1)).endGesture();
-    }
-
-    static class FakeLocationView extends View {
-        private final int mX;
-        private final int mY;
-
-        public FakeLocationView(Context context, int x, int y) {
-            super(context);
-            mX = x;
-            mY = y;
-        }
-
-        @Override
-        public void getLocationInWindow(int[] outLocation) {
-            outLocation[0] = mX;
-            outLocation[1] = mY;
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 41e82cb..51fb47b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -152,7 +152,6 @@
         bubbleSbn.getNotification().contentIntent = mContentIntent;
         bubbleSbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
         // Do what BubbleController's NotificationEntryListener#onPendingEntryAdded does:
-        mBubbleNotificationRow.getEntry().setIsBubble(true);
         mBubbleNotificationRow.getEntry().setShowInShadeWhenBubble(true);
 
         mActiveNotifications = new ArrayList<>();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index cd9069a..3edfb56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -95,7 +95,7 @@
         triggerConstantsOnChange();
         assertEquals(false, mConstants.requiresTargetingP());
 
-        overrideSetting(SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, "");
+        overrideSetting(SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, null);
         triggerConstantsOnChange();
         assertEquals(true, mConstants.requiresTargetingP());
     }
@@ -223,21 +223,21 @@
 
     private void resetAllDeviceConfigFlags() {
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.SSIN_ENABLED, "", false /* makeDefault */);
+                SystemUiDeviceConfigFlags.SSIN_ENABLED, null, false /* makeDefault */);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, "", false /* makeDefault */);
+                SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, null, false /* makeDefault */);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.SSIN_MAX_SQUEEZE_REMEASURE_ATTEMPTS, "",
+                SystemUiDeviceConfigFlags.SSIN_MAX_SQUEEZE_REMEASURE_ATTEMPTS, null,
                 false /* makeDefault */);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.SSIN_EDIT_CHOICES_BEFORE_SENDING, "",
+                SystemUiDeviceConfigFlags.SSIN_EDIT_CHOICES_BEFORE_SENDING, null,
                 false /* makeDefault */);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.SSIN_SHOW_IN_HEADS_UP, "", false /* makeDefault */);
+                SystemUiDeviceConfigFlags.SSIN_SHOW_IN_HEADS_UP, null, false /* makeDefault */);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.SSIN_MIN_NUM_SYSTEM_GENERATED_REPLIES, "",
+                SystemUiDeviceConfigFlags.SSIN_MIN_NUM_SYSTEM_GENERATED_REPLIES, null,
                 false /* makeDefault */);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.SSIN_MAX_NUM_ACTIONS, "", false /* makeDefault */);
+                SystemUiDeviceConfigFlags.SSIN_MAX_NUM_ACTIONS, null, false /* makeDefault */);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
index 51149ab..f6b24da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
@@ -19,7 +19,8 @@
 import com.android.systemui.statusbar.policy.CastController;
 import com.android.systemui.statusbar.policy.CastController.Callback;
 
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.List;
 
 public class FakeCastController extends BaseLeakChecker<Callback> implements CastController {
     public FakeCastController(LeakCheck test) {
@@ -37,8 +38,8 @@
     }
 
     @Override
-    public Set<CastDevice> getCastDevices() {
-        return null;
+    public List<CastDevice> getCastDevices() {
+        return new ArrayList<>();
     }
 
     @Override
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
index 23d2e7b..704ff2e 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
@@ -31,6 +31,6 @@
     <bool name="config_navBarTapThrough">true</bool>
 
     <!-- Controls the size of the back gesture inset. -->
-    <dimen name="config_backGestureInset">48dp</dimen>
+    <dimen name="config_backGestureInset">20dp</dimen>
 
 </resources>
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index ce39a70..a88ae9e 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -7162,6 +7162,19 @@
     // Open: Settings > app > bubble settings > confirmation dialog
     DIALOG_APP_BUBBLE_SETTINGS = 1702;
 
+    // ACTION: Display white balance setting enabled or disabled.
+    // CATEGORY: SETTINGS
+    // OS: Q
+    ACTION_DISPLAY_WHITE_BALANCE_SETTING_CHANGED = 1703;
+
+    // Action: ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET
+    // Direct share target hashed with rotating salt
+    FIELD_HASHED_TARGET_NAME = 1704;
+
+    // Action: ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET
+    // Salt generation for the above hashed direct share target
+    FIELD_HASHED_TARGET_SALT_GEN = 1705;
+
     // ---- End Q Constants, all Q constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 2e58ac2..bd72976 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -2064,6 +2064,10 @@
 
   // What event triggered WifiUsabilityStats.
   optional UsabilityStatsTriggerType trigger_type = 3;
+
+  // Firmware alert code. Only valid when the stats was triggered by a firmware
+  // alert, otherwise -1.
+  optional int32 firmware_alert_code = 4 [default = -1];
 }
 
 message DeviceMobilityStatePnoScanStats {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3c69bb7..6e2c228 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -4126,6 +4126,13 @@
             return mBindingServices;
         }
 
+        /**
+         * Returns enabled service list.
+         */
+        public Set<ComponentName> getEnabledServicesLocked() {
+            return mEnabledServices;
+        }
+
         public int getSoftKeyboardShowMode() {
             return mSoftKeyboardShowMode;
         }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 3bd6220..b66caa5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -123,12 +123,12 @@
         synchronized (mLock) {
             UserState userState = mUserStateWeakReference.get();
             if (userState == null) return;
-            if (userState.mEnabledServices.remove(mComponentName)) {
+            if (userState.getEnabledServicesLocked().remove(mComponentName)) {
                 final long identity = Binder.clearCallingIdentity();
                 try {
                     mSystemSupport.persistComponentNamesToSettingLocked(
                             Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                            userState.mEnabledServices, userState.mUserId);
+                            userState.getEnabledServicesLocked(), userState.mUserId);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
@@ -183,6 +183,14 @@
                 mWasConnectedAndDied = false;
                 serviceInterface = mServiceInterface;
             }
+            // There's a chance that service is removed from enabled_accessibility_services setting
+            // key, but skip unbinding because of it's in binding state. Unbinds it if it's
+            // not in enabled service list.
+            if (serviceInterface != null
+                    && !userState.getEnabledServicesLocked().contains(mComponentName)) {
+                mSystemSupport.onClientChangeLocked(false);
+                return;
+            }
         }
         if (serviceInterface == null) {
             binderDied();
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 4bbf682..72c84e2 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -64,9 +64,7 @@
                 public void binderDied() {
                     mUiAutomationServiceOwner.unlinkToDeath(this, 0);
                     mUiAutomationServiceOwner = null;
-                    if (mUiAutomationService != null) {
-                        destroyUiAutomationService();
-                    }
+                    destroyUiAutomationService();
                 }
             };
 
@@ -201,17 +199,20 @@
 
     private void destroyUiAutomationService() {
         synchronized (mLock) {
-            mUiAutomationService.mServiceInterface.asBinder().unlinkToDeath(mUiAutomationService,
-                    0);
-            mUiAutomationService.onRemoved();
-            mUiAutomationService.resetLocked();
-            mUiAutomationService = null;
-            mUiAutomationFlags = 0;
-            if (mUiAutomationServiceOwner != null) {
-                mUiAutomationServiceOwner.unlinkToDeath(mUiAutomationServiceOwnerDeathRecipient, 0);
-                mUiAutomationServiceOwner = null;
+            if (mUiAutomationService != null) {
+                mUiAutomationService.mServiceInterface.asBinder().unlinkToDeath(
+                        mUiAutomationService, 0);
+                mUiAutomationService.onRemoved();
+                mUiAutomationService.resetLocked();
+                mUiAutomationService = null;
+                mUiAutomationFlags = 0;
+                if (mUiAutomationServiceOwner != null) {
+                    mUiAutomationServiceOwner.unlinkToDeath(
+                            mUiAutomationServiceOwnerDeathRecipient, 0);
+                    mUiAutomationServiceOwner = null;
+                }
+                mSystemSupport.onClientChangeLocked(false);
             }
-            mSystemSupport.onClientChangeLocked(false);
         }
     }
 
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 7106664..79afe8e 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -31,11 +31,14 @@
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.os.Binder;
+import android.os.FileUtils;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
@@ -49,6 +52,7 @@
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 
+import java.io.File;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Collections;
@@ -86,6 +90,18 @@
 
     private Set<ComponentName> mTransportWhitelist;
 
+    private final BroadcastReceiver mUserRemovedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+                if (userId > 0) { // for only non system users
+                    onRemovedNonSystemUser(userId);
+                }
+            }
+        }
+    };
+
     /** Instantiate a new instance of {@link BackupManagerService}. */
     public BackupManagerService(
             Context context, Trampoline trampoline, HandlerThread backupThread) {
@@ -99,6 +115,23 @@
         if (mTransportWhitelist == null) {
             mTransportWhitelist = Collections.emptySet();
         }
+
+        mContext.registerReceiver(mUserRemovedReceiver,
+                new IntentFilter(Intent.ACTION_USER_REMOVED));
+    }
+
+    /**
+     * Remove backup state for non system {@code userId} when the user is removed from the device.
+     * For non system users, backup state is stored in both the user's own dir and the system dir.
+     * When the user is removed, the user's own dir gets removed by the OS. This method ensures that
+     * the part of the user backup state which is in the system dir also gets removed.
+     */
+    private void onRemovedNonSystemUser(int userId) {
+        Slog.i(TAG, "Removing state for non system user " + userId);
+        File dir = UserBackupManagerFiles.getStateDirInSystemDir(userId);
+        if (!FileUtils.deleteContentsAndDir(dir)) {
+            Slog.w(TAG, "Failed to delete state dir for removed user: " + userId);
+        }
     }
 
     /**
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index bb0aba0..a9b292b3 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -47,7 +47,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.DumpUtils;
-import com.android.server.backup.utils.FileUtils;
 import com.android.server.backup.utils.RandomAccessFileUtils;
 
 import java.io.File;
@@ -95,7 +94,7 @@
      * Name of file for non-system users that remembers whether backup was explicitly activated or
      * deactivated with a call to setBackupServiceActive.
      */
-    private static final String REMEMBER_ACTIVATED_FILENAME_PREFIX = "backup-remember-activated";
+    private static final String REMEMBER_ACTIVATED_FILENAME = "backup-remember-activated";
 
     // Product-level suppression of backup/restore.
     private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable";
@@ -143,8 +142,7 @@
 
     /** Stored in the system user's directory and the file is indexed by the user it refers to. */
     protected File getRememberActivatedFileForNonSystemUser(int userId) {
-        return FileUtils.createNewFile(UserBackupManagerFiles.getStateFileInSystemDir(
-                REMEMBER_ACTIVATED_FILENAME_PREFIX, userId));
+        return UserBackupManagerFiles.getStateFileInSystemDir(REMEMBER_ACTIVATED_FILENAME, userId);
     }
 
     /** Stored in the system user's directory and the file is indexed by the user it refers to. */
@@ -336,8 +334,13 @@
         // action since we need to remember that a permissioned call was made irrespective of
         // whether the call changes the state or not.
         if (userId != UserHandle.USER_SYSTEM) {
-            RandomAccessFileUtils.writeBoolean(getRememberActivatedFileForNonSystemUser(userId),
-                    makeActive);
+            try {
+                File rememberFile = getRememberActivatedFileForNonSystemUser(userId);
+                createFile(rememberFile);
+                RandomAccessFileUtils.writeBoolean(rememberFile, makeActive);
+            } catch (IOException e) {
+                Slog.e(TAG, "Unable to persist backup service activity", e);
+            }
         }
 
         if (mGlobalDisable) {
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerFiles.java b/services/backup/java/com/android/server/backup/UserBackupManagerFiles.java
index 4638ac6..8e60542 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerFiles.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerFiles.java
@@ -49,8 +49,13 @@
         return new File(Environment.getDownloadCacheDirectory(), BACKUP_STAGING_DIR);
     }
 
-    /** Stored in the system user's directory and the file is indexed by the user it refers to. */
-    static File getStateFileInSystemDir(String prefix, int userId) {
-        return new File(getBaseStateDir(UserHandle.USER_SYSTEM), prefix + "-" + userId);
+    /** A user specific dir within the system user's directory. */
+    static File getStateDirInSystemDir(int userId) {
+        return new File(getBaseStateDir(UserHandle.USER_SYSTEM), "" + userId);
+    }
+
+    /** Stored in a user specific dir within the system user's directory. */
+    static File getStateFileInSystemDir(String filename, int userId) {
+        return new File(getStateDirInSystemDir(userId), filename);
     }
 }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a7404bc..a3e7d36 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -164,6 +164,20 @@
     }
 
     @Override
+    public void onUnlockUser(int userHandle) {
+        Set<Association> associations = readAllAssociations(userHandle);
+        Set<String> companionAppPackages = new HashSet<>();
+        for (Association association : associations) {
+            companionAppPackages.add(association.companionAppPackage);
+        }
+        ActivityTaskManagerInternal atmInternal = LocalServices.getService(
+                ActivityTaskManagerInternal.class);
+        if (atmInternal != null) {
+            atmInternal.setCompanionAppPackages(userHandle, companionAppPackages);
+        }
+    }
+
+    @Override
     public void binderDied() {
         Handler.getMain().post(this::cleanup);
     }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 8ad23b0..6405254 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -951,7 +951,7 @@
 
         mTethering = makeTethering();
 
-        mPermissionMonitor = new PermissionMonitor(mContext, mNMS);
+        mPermissionMonitor = new PermissionMonitor(mContext, mNMS, mNetd);
 
         // Set up the listener for user state for creating user VPNs.
         // Should run on mHandler to avoid any races.
@@ -6382,6 +6382,11 @@
                 Slog.wtf(TAG, networkAgent.name() + " connected with null LinkProperties");
             }
 
+            // NetworkCapabilities need to be set before sending the private DNS config to
+            // NetworkMonitor, otherwise NetworkMonitor cannot determine if validation is required.
+            synchronized (networkAgent) {
+                networkAgent.setNetworkCapabilities(networkAgent.networkCapabilities);
+            }
             handlePerNetworkPrivateDnsConfig(networkAgent, mDnsManager.getPrivateDnsConfig());
             updateLinkProperties(networkAgent, new LinkProperties(networkAgent.linkProperties),
                     null);
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index f5bd11c..99bbcf8 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -70,25 +70,24 @@
         checkPermission();
         if (!"running".equals(SystemProperties.get("init.svc.gsid"))) {
             SystemProperties.set("ctl.start", "gsid");
-        }
-        for (int sleepMs = 64; sleepMs <= (GSID_ROUGH_TIMEOUT_MS << 1); sleepMs <<= 1) {
-            try {
-                Thread.sleep(sleepMs);
-            } catch (InterruptedException e) {
-                Slog.e(TAG, "Interrupted when waiting for GSID");
-                break;
-            }
-            if ("running".equals(SystemProperties.get("init.svc.gsid"))) {
-                synchronized (this) {
-                    if (mGsiService == null) {
-                        mGsiService = connect(this);
-                    }
-                    return mGsiService;
+            for (int sleepMs = 64; sleepMs <= (GSID_ROUGH_TIMEOUT_MS << 1); sleepMs <<= 1) {
+                try {
+                    Thread.sleep(sleepMs);
+                } catch (InterruptedException e) {
+                    Slog.e(TAG, "Interrupted when waiting for GSID");
+                    break;
+                }
+                if ("running".equals(SystemProperties.get("init.svc.gsid"))) {
+                    break;
                 }
             }
         }
-        Slog.e(TAG, "Unable to start gsid");
-        return null;
+        synchronized (this) {
+            if (mGsiService == null) {
+                mGsiService = connect(this);
+            }
+            return mGsiService;
+        }
     }
 
     private void checkPermission() {
@@ -125,19 +124,24 @@
     }
 
     @Override
+    public boolean isEnabled() throws RemoteException {
+        return getGsiService().isGsiEnabled();
+    }
+
+    @Override
     public boolean remove() throws RemoteException {
         return getGsiService().removeGsiInstall();
     }
 
     @Override
-    public boolean toggle() throws RemoteException {
+    public boolean setEnable(boolean enable) throws RemoteException {
         IGsiService gsiService = getGsiService();
-        if (gsiService.isGsiRunning()) {
-            return gsiService.disableGsiInstall();
-        } else {
+        if (enable) {
             final int status = gsiService.getGsiBootStatus();
             final boolean singleBoot = (status == IGsiService.BOOT_STATUS_SINGLE_BOOT);
             return gsiService.setGsiBootable(singleBoot) == 0;
+        } else {
+            return gsiService.disableGsiInstall();
         }
     }
 
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index be58389..3410d8d 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -52,6 +52,7 @@
 import android.location.Criteria;
 import android.location.GeocoderParams;
 import android.location.Geofence;
+import android.location.GnssCapabilities;
 import android.location.GnssMeasurementCorrections;
 import android.location.IBatchedLocationCallback;
 import android.location.IGnssMeasurementsListener;
@@ -102,6 +103,7 @@
 import com.android.server.location.GeofenceManager;
 import com.android.server.location.GeofenceProxy;
 import com.android.server.location.GnssBatchingProvider;
+import com.android.server.location.GnssCapabilitiesProvider;
 import com.android.server.location.GnssLocationProvider;
 import com.android.server.location.GnssMeasurementCorrectionsProvider;
 import com.android.server.location.GnssMeasurementsProvider;
@@ -249,8 +251,8 @@
     private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM};
 
     private GnssLocationProvider.GnssSystemInfoProvider mGnssSystemInfoProvider;
-
     private GnssLocationProvider.GnssMetricsProvider mGnssMetricsProvider;
+    private GnssCapabilitiesProvider mGnssCapabilitiesProvider;
 
     private GnssBatchingProvider mGnssBatchingProvider;
     @GuardedBy("mLock")
@@ -773,6 +775,7 @@
             mGnssSystemInfoProvider = gnssProvider.getGnssSystemInfoProvider();
             mGnssBatchingProvider = gnssProvider.getGnssBatchingProvider();
             mGnssMetricsProvider = gnssProvider.getGnssMetricsProvider();
+            mGnssCapabilitiesProvider = gnssProvider.getGnssCapabilitiesProvider();
             mGnssStatusProvider = gnssProvider.getGnssStatusProvider();
             mNetInitiatedListener = gnssProvider.getNetInitiatedListener();
             mGnssMeasurementsProvider = gnssProvider.getGnssMeasurementsProvider();
@@ -2970,10 +2973,10 @@
         mContext.enforceCallingPermission(
                 android.Manifest.permission.LOCATION_HARDWARE,
                 "Location Hardware permission not granted to obtain GNSS chipset capabilities.");
-        if (!hasGnssPermissions(packageName) || mGnssMeasurementCorrectionsProvider == null) {
-            return -1;
+        if (!hasGnssPermissions(packageName) || mGnssCapabilitiesProvider == null) {
+            return GnssCapabilities.INVALID_CAPABILITIES;
         }
-        return mGnssMeasurementCorrectionsProvider.getCapabilities();
+        return mGnssCapabilitiesProvider.getGnssCapabilities();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index d1ae284..3dc8af1 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -89,10 +89,10 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
-import com.android.internal.net.NetworkStatsFactory;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.HexDump;
 import com.android.internal.util.Preconditions;
+import com.android.server.net.NetworkStatsFactory;
 
 import com.google.android.collect.Maps;
 
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
index a5a515f..e3dc3b7 100644
--- a/services/core/java/com/android/server/ServiceWatcher.java
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -382,7 +382,13 @@
     /**
      * Runs the given function synchronously if currently connected, and returns the default value
      * if not currently connected or if any exception is thrown.
+     *
+     * @deprecated Using this function is an indication that your AIDL API is broken. Calls from
+     * system server to outside MUST be one-way, and so cannot return any result, and this
+     * method should not be needed or used. Use a separate callback interface to allow outside
+     * components to return results back to the system server.
      */
+    @Deprecated
     public final <T> T runOnBinderBlocking(BlockingBinderRunner<T> runner, T defaultValue) {
         try {
             return runOnHandlerBlocking(() -> {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 0786b18..3079192 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -929,13 +929,20 @@
     private void initIfBootedAndConnected() {
         Slog.d(TAG, "Thinking about init, mBootCompleted=" + mBootCompleted
                 + ", mDaemonConnected=" + mDaemonConnected);
-        if (mBootCompleted && mDaemonConnected
-                && !StorageManager.isFileEncryptedNativeOnly()) {
-            // When booting a device without native support, make sure that our
-            // user directories are locked or unlocked based on the current
-            // emulation status.
-            final boolean initLocked = StorageManager.isFileEncryptedEmulatedOnly();
-            Slog.d(TAG, "Setting up emulation state, initlocked=" + initLocked);
+        if (mBootCompleted && mDaemonConnected) {
+            // Tell vold to lock or unlock the user directories based on the
+            // current file-based encryption status.
+            final boolean initLocked;
+            if (StorageManager.isFileEncryptedNativeOrEmulated()) {
+                // For native FBE this is a no-op after reboot, but this is
+                // still needed in case of framework restarts.
+                Slog.d(TAG, "FBE is enabled; ensuring all user directories are locked.");
+                initLocked = true;
+            } else {
+                // This is in case FBE emulation was turned off.
+                Slog.d(TAG, "FBE is disabled; ensuring the FBE emulation state is cleared.");
+                initLocked = false;
+            }
             final List<UserInfo> users = mContext.getSystemService(UserManager.class).getUsers();
             for (UserInfo user : users) {
                 try {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 261ed4c..f0982d3 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -1344,7 +1344,8 @@
                     if (!r.isForeground) {
                         final ServiceMap smap = getServiceMapLocked(r.userId);
                         if (smap != null) {
-                            ActiveForegroundApp active = smap.mActiveForegroundApps.get(r.packageName);
+                            ActiveForegroundApp active = smap.mActiveForegroundApps
+                                    .get(r.packageName);
                             if (active == null) {
                                 active = new ActiveForegroundApp();
                                 active.mPackageName = r.packageName;
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index e42666c..7ab70fa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -49,6 +49,7 @@
     static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || false;
     static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;
     static final boolean DEBUG_BROADCAST_DEFERRAL = DEBUG_BROADCAST || false;
+    static final boolean DEBUG_COMPACTION = DEBUG_ALL || false;
     static final boolean DEBUG_LRU = DEBUG_ALL || false;
     static final boolean DEBUG_MU = DEBUG_ALL || false;
     static final boolean DEBUG_NETWORK = DEBUG_ALL || false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3eb7c03..1757c98 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -213,6 +213,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManagerInternal.CheckPermissionDelegate;
+import android.content.pm.PackageParser;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PathPermission;
 import android.content.pm.PermissionInfo;
@@ -8772,6 +8773,8 @@
                     com.android.internal.R.bool.config_customUserSwitchUi);
             mUserController.mMaxRunningUsers = res.getInteger(
                     com.android.internal.R.integer.config_multiuserMaxRunningUsers);
+            mUserController.mDelayUserDataLocking = res.getBoolean(
+                    com.android.internal.R.bool.config_multiuserDelayUserDataLocking);
 
             mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs;
         }
@@ -15333,18 +15336,20 @@
             final ProcessRecord callerApp = getRecordForAppLocked(caller);
             final int callingPid = Binder.getCallingPid();
             final int callingUid = Binder.getCallingUid();
+
             final long origId = Binder.clearCallingIdentity();
-            int res = broadcastIntentLocked(callerApp,
-                    callerApp != null ? callerApp.info.packageName : null,
-                    intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
-                    requiredPermissions, appOp, bOptions, serialized, sticky,
-                    callingPid, callingUid, callingUid, callingPid, userId);
-            Binder.restoreCallingIdentity(origId);
-            return res;
+            try {
+                return broadcastIntentLocked(callerApp,
+                        callerApp != null ? callerApp.info.packageName : null,
+                        intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
+                        requiredPermissions, appOp, bOptions, serialized, sticky,
+                        callingPid, callingUid, callingUid, callingPid, userId);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
         }
     }
 
-
     int broadcastIntentInPackage(String packageName, int uid, int realCallingUid,
             int realCallingPid, Intent intent, String resolvedType, IIntentReceiver resultTo,
             int resultCode, String resultData, Bundle resultExtras,
@@ -15356,13 +15361,15 @@
             final long origId = Binder.clearCallingIdentity();
             String[] requiredPermissions = requiredPermission == null ? null
                     : new String[] {requiredPermission};
-            int res = broadcastIntentLocked(null, packageName, intent, resolvedType,
-                    resultTo, resultCode, resultData, resultExtras,
-                    requiredPermissions, OP_NONE, bOptions, serialized,
-                    sticky, -1, uid, realCallingUid, realCallingPid, userId,
-                    allowBackgroundActivityStarts);
-            Binder.restoreCallingIdentity(origId);
-            return res;
+            try {
+                return broadcastIntentLocked(null, packageName, intent, resolvedType,
+                        resultTo, resultCode, resultData, resultExtras,
+                        requiredPermissions, OP_NONE, bOptions, serialized,
+                        sticky, -1, uid, realCallingUid, realCallingPid, userId,
+                        allowBackgroundActivityStarts);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
         }
     }
 
@@ -18468,7 +18475,9 @@
 
     void updateApplicationInfoLocked(@NonNull List<String> packagesToUpdate, int userId) {
         final boolean updateFrameworkRes = packagesToUpdate.contains("android");
-
+        if (updateFrameworkRes) {
+            PackageParser.readConfigUseRoundIcon(null);
+        }
         mProcessList.updateApplicationInfoLocked(packagesToUpdate, userId, updateFrameworkRes);
 
         if (updateFrameworkRes) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 8a462da..d1379b6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM;
 import static android.app.ActivityTaskManager.RESIZE_MODE_USER;
@@ -427,8 +428,12 @@
                 if (intent.getComponent() != null) {
                     packageName = intent.getComponent().getPackageName();
                 } else {
+                    // queryIntentActivities does not convert user id, so we convert it here first
+                    int userIdForQuery = mInternal.mUserController.handleIncomingUser(
+                            Binder.getCallingPid(), Binder.getCallingUid(), mUserId, false,
+                            ALLOW_NON_FULL, "ActivityManagerShellCommand", null);
                     List<ResolveInfo> activities = mPm.queryIntentActivities(intent, mimeType, 0,
-                            mUserId).getList();
+                            userIdForQuery).getList();
                     if (activities == null || activities.size() <= 0) {
                         getErrPrintWriter().println("Error: Intent does not match any activities: "
                                 + intent);
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/AppCompactor.java
index f58fb95..043dc8d 100644
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ b/services/core/java/com/android/server/am/AppCompactor.java
@@ -18,6 +18,9 @@
 
 import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
 
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.os.Debug;
@@ -30,6 +33,7 @@
 import android.provider.DeviceConfig.OnPropertyChangedListener;
 import android.text.TextUtils;
 import android.util.EventLog;
+import android.util.Slog;
 import android.util.StatsLog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -39,7 +43,12 @@
 import java.io.FileOutputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.Random;
+import java.util.Set;
 
 public final class AppCompactor {
 
@@ -55,6 +64,12 @@
     @VisibleForTesting static final String KEY_COMPACT_THROTTLE_6 = "compact_throttle_6";
     @VisibleForTesting static final String KEY_COMPACT_STATSD_SAMPLE_RATE =
             "compact_statsd_sample_rate";
+    @VisibleForTesting static final String KEY_COMPACT_FULL_RSS_THROTTLE_KB =
+            "compact_full_rss_throttle_kb";
+    @VisibleForTesting static final String KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB =
+            "compact_full_delta_rss_throttle_kb";
+    @VisibleForTesting static final String KEY_COMPACT_PROC_STATE_THROTTLE =
+            "compact_proc_state_throttle";
 
     // Phenotype sends int configurations and we map them to the strings we'll use on device,
     // preventing a weird string value entering the kernel.
@@ -79,6 +94,11 @@
     @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_6 = 10 * 60 * 1000;
     // The sampling rate to push app compaction events into statsd for upload.
     @VisibleForTesting static final float DEFAULT_STATSD_SAMPLE_RATE = 0.1f;
+    @VisibleForTesting static final long DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB = 12_000L;
+    @VisibleForTesting static final long DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB = 8_000L;
+    // Format of this string should be a comma separated list of integers.
+    @VisibleForTesting static final String DEFAULT_COMPACT_PROC_STATE_THROTTLE =
+            String.valueOf(ActivityManager.PROCESS_STATE_RECEIVER);
 
     @VisibleForTesting
     interface PropertyChangedCallbackForTest {
@@ -123,6 +143,12 @@
                             updateCompactionThrottles();
                         } else if (KEY_COMPACT_STATSD_SAMPLE_RATE.equals(name)) {
                             updateStatsdSampleRate();
+                        } else if (KEY_COMPACT_FULL_RSS_THROTTLE_KB.equals(name)) {
+                            updateFullRssThrottle();
+                        } else if (KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB.equals(name)) {
+                            updateFullDeltaRssThrottle();
+                        } else if (KEY_COMPACT_PROC_STATE_THROTTLE.equals(name)) {
+                            updateProcStateThrottle();
                         }
                     }
                     if (mTestCallback != null) {
@@ -154,18 +180,42 @@
     @VisibleForTesting volatile long mCompactThrottlePersistent = DEFAULT_COMPACT_THROTTLE_6;
     @GuardedBy("mPhenotypeFlagLock")
     private volatile boolean mUseCompaction = DEFAULT_USE_COMPACTION;
-
     private final Random mRandom = new Random();
     @GuardedBy("mPhenotypeFlagLock")
     @VisibleForTesting volatile float mStatsdSampleRate = DEFAULT_STATSD_SAMPLE_RATE;
+    @GuardedBy("mPhenotypeFlagLock")
+    @VisibleForTesting volatile long mFullAnonRssThrottleKb =
+            DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
+    @GuardedBy("mPhenoypeFlagLock")
+    @VisibleForTesting volatile long mFullDeltaRssThrottleKb =
+            DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
+    @GuardedBy("mPhenoypeFlagLock")
+    @VisibleForTesting final Set<Integer> mProcStateThrottle;
 
     // Handler on which compaction runs.
     private Handler mCompactionHandler;
 
+    // Maps process ID to last compaction statistics for processes that we've fully compacted. Used
+    // when evaluating throttles that we only consider for "full" compaction, so we don't store
+    // data for "some" compactions.
+    private Map<Integer, LastCompactionStats> mLastCompactionStats =
+            new LinkedHashMap<Integer, LastCompactionStats>() {
+                @Override
+                protected boolean removeEldestEntry(Map.Entry eldest) {
+                    return size() > 100;
+                }
+    };
+
+    private int mSomeCompactionCount;
+    private int mFullCompactionCount;
+    private int mPersistentCompactionCount;
+    private int mBfgsCompactionCount;
+
     public AppCompactor(ActivityManagerService am) {
         mAm = am;
         mCompactionThread = new ServiceThread("CompactionThread",
                 THREAD_PRIORITY_FOREGROUND, true);
+        mProcStateThrottle = new HashSet<>();
     }
 
     @VisibleForTesting
@@ -186,10 +236,12 @@
             updateCompactionActions();
             updateCompactionThrottles();
             updateStatsdSampleRate();
+            updateFullRssThrottle();
+            updateFullDeltaRssThrottle();
+            updateProcStateThrottle();
         }
         Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(),
                 Process.THREAD_GROUP_SYSTEM);
-
     }
 
     /**
@@ -212,7 +264,33 @@
             pw.println("  " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull);
             pw.println("  " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome);
             pw.println("  " + KEY_COMPACT_THROTTLE_4 + "=" + mCompactThrottleFullFull);
+            pw.println("  " + KEY_COMPACT_THROTTLE_5 + "=" + mCompactThrottleBFGS);
+            pw.println("  " + KEY_COMPACT_THROTTLE_6 + "=" + mCompactThrottlePersistent);
             pw.println("  " + KEY_COMPACT_STATSD_SAMPLE_RATE + "=" + mStatsdSampleRate);
+            pw.println("  " + KEY_COMPACT_FULL_RSS_THROTTLE_KB + "="
+                    + mFullAnonRssThrottleKb);
+            pw.println("  " + KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + "="
+                    + mFullDeltaRssThrottleKb);
+            pw.println("  "  + KEY_COMPACT_PROC_STATE_THROTTLE + "="
+                    + Arrays.toString(mProcStateThrottle.toArray(new Integer[0])));
+
+            pw.println("  " + mSomeCompactionCount + " some, " + mFullCompactionCount
+                    + " full, " + mPersistentCompactionCount + " persistent, "
+                    + mBfgsCompactionCount + " BFGS compactions.");
+
+            if (mLastCompactionStats != null) {
+                pw.println("  Tracking last compaction stats for " + mLastCompactionStats.size()
+                        + " processes.");
+                if (DEBUG_COMPACTION) {
+                    for (Map.Entry<Integer, LastCompactionStats> entry
+                            : mLastCompactionStats.entrySet()) {
+                        int pid = entry.getKey();
+                        LastCompactionStats stats = entry.getValue();
+                        pw.println("    " + pid + ": "
+                                + Arrays.toString(stats.getRssAfterCompaction()));
+                    }
+                }
+            }
         }
     }
 
@@ -277,7 +355,7 @@
 
     /**
      * Reads the flag value from DeviceConfig to determine whether app compaction
-     * should be enabled, and starts/stops the compaction thread as needed.
+     * should be enabled, and starts the compaction thread if needed.
      */
     @GuardedBy("mPhenotypeFlagLock")
     private void updateUseCompaction() {
@@ -360,6 +438,58 @@
         mStatsdSampleRate = Math.min(1.0f, Math.max(0.0f, mStatsdSampleRate));
     }
 
+    @GuardedBy("mPhenotypeFlagLock")
+    private void updateFullRssThrottle() {
+        mFullAnonRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_COMPACT_FULL_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+
+        // Don't allow negative values. 0 means don't apply the throttle.
+        if (mFullAnonRssThrottleKb < 0) {
+            mFullAnonRssThrottleKb = DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB;
+        }
+    }
+
+    @GuardedBy("mPhenotypeFlagLock")
+    private void updateFullDeltaRssThrottle() {
+        mFullDeltaRssThrottleKb = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+
+        if (mFullDeltaRssThrottleKb < 0) {
+            mFullDeltaRssThrottleKb = DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB;
+        }
+    }
+
+    @GuardedBy("mPhenotypeFlagLock")
+    private void updateProcStateThrottle() {
+        String procStateThrottleString = DeviceConfig.getString(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_COMPACT_PROC_STATE_THROTTLE,
+                DEFAULT_COMPACT_PROC_STATE_THROTTLE);
+        if (!parseProcStateThrottle(procStateThrottleString)) {
+            Slog.w(TAG_AM, "Unable to parse app compact proc state throttle \""
+                    + procStateThrottleString + "\" falling back to default.");
+            if (!parseProcStateThrottle(DEFAULT_COMPACT_PROC_STATE_THROTTLE)) {
+                Slog.wtf(TAG_AM,
+                        "Unable to parse default app compact proc state throttle "
+                                + DEFAULT_COMPACT_PROC_STATE_THROTTLE);
+            }
+        }
+    }
+
+    private boolean parseProcStateThrottle(String procStateThrottleString) {
+        String[] procStates = TextUtils.split(procStateThrottleString, ",");
+        mProcStateThrottle.clear();
+        for (String procState : procStates) {
+            try {
+                mProcStateThrottle.add(Integer.parseInt(procState));
+            } catch (NumberFormatException e) {
+                Slog.e(TAG_AM, "Failed to parse default app compaction proc state: "
+                        + procState);
+                return false;
+            }
+        }
+        return true;
+    }
+
     @VisibleForTesting
     static String compactActionIntToString(int action) {
         switch(action) {
@@ -376,6 +506,22 @@
         }
     }
 
+    @VisibleForTesting static String procStateListToString(Integer... processStates) {
+        return Arrays.toString(processStates);
+    }
+
+    private static final class LastCompactionStats {
+        private final long[] mRssAfterCompaction;
+
+        LastCompactionStats(long[] rss) {
+            mRssAfterCompaction = rss;
+        }
+
+        long[] getRssAfterCompaction() {
+            return mRssAfterCompaction;
+        }
+    }
+
     private final class MemCompactionHandler extends Handler {
         private MemCompactionHandler() {
             super(mCompactionThread.getLooper());
@@ -392,24 +538,34 @@
                     final String name;
                     int pendingAction, lastCompactAction;
                     long lastCompactTime;
+                    LastCompactionStats lastCompactionStats;
+                    int lastOomAdj = msg.arg1;
+                    int procState = msg.arg2;
                     synchronized (mAm) {
                         proc = mPendingCompactionProcesses.remove(0);
 
                         pendingAction = proc.reqCompactAction;
+                        pid = proc.pid;
+                        name = proc.processName;
 
                         // don't compact if the process has returned to perceptible
                         // and this is only a cached/home/prev compaction
                         if ((pendingAction == COMPACT_PROCESS_SOME
                                 || pendingAction == COMPACT_PROCESS_FULL)
                                 && (proc.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ)) {
+                            if (DEBUG_COMPACTION) {
+                                Slog.d(TAG_AM,
+                                        "Skipping compaction as process " + name + " is "
+                                        + "now perceptible.");
+                            }
                             return;
                         }
 
-                        pid = proc.pid;
-                        name = proc.processName;
-
                         lastCompactAction = proc.lastCompactAction;
                         lastCompactTime = proc.lastCompactTime;
+                        // remove rather than get so that insertion order will be updated when we
+                        // put the post-compaction stats back into the map.
+                        lastCompactionStats = mLastCompactionStats.remove(pid);
                     }
 
                     if (pid == 0) {
@@ -431,6 +587,12 @@
                                     || (lastCompactAction == COMPACT_PROCESS_FULL
                                         && (start - lastCompactTime
                                                 < mCompactThrottleSomeFull))) {
+                                if (DEBUG_COMPACTION) {
+                                    Slog.d(TAG_AM, "Skipping some compaction for " + name
+                                            + ": too soon. throttle=" + mCompactThrottleSomeSome
+                                            + "/" + mCompactThrottleSomeFull + " last="
+                                            + (start - lastCompactTime) + "ms ago");
+                                }
                                 return;
                             }
                         } else if (pendingAction == COMPACT_PROCESS_FULL) {
@@ -439,18 +601,35 @@
                                     || (lastCompactAction == COMPACT_PROCESS_FULL
                                         && (start - lastCompactTime
                                                 < mCompactThrottleFullFull))) {
+                                if (DEBUG_COMPACTION) {
+                                    Slog.d(TAG_AM, "Skipping full compaction for " + name
+                                            + ": too soon. throttle=" + mCompactThrottleFullSome
+                                            + "/" + mCompactThrottleFullFull + " last="
+                                            + (start - lastCompactTime) + "ms ago");
+                                }
                                 return;
                             }
                         } else if (pendingAction == COMPACT_PROCESS_PERSISTENT) {
                             if (start - lastCompactTime < mCompactThrottlePersistent) {
+                                if (DEBUG_COMPACTION) {
+                                    Slog.d(TAG_AM, "Skipping persistent compaction for " + name
+                                            + ": too soon. throttle=" + mCompactThrottlePersistent
+                                            + " last=" + (start - lastCompactTime) + "ms ago");
+                                }
                                 return;
                             }
                         } else if (pendingAction == COMPACT_PROCESS_BFGS) {
                             if (start - lastCompactTime < mCompactThrottleBFGS) {
+                                if (DEBUG_COMPACTION) {
+                                    Slog.d(TAG_AM, "Skipping bfgs compaction for " + name
+                                            + ": too soon. throttle=" + mCompactThrottleBFGS
+                                            + " last=" + (start - lastCompactTime) + "ms ago");
+                                }
                                 return;
                             }
                         }
                     }
+
                     switch (pendingAction) {
                         case COMPACT_PROCESS_SOME:
                             action = mCompactActionSome;
@@ -470,12 +649,77 @@
                         return;
                     }
 
+                    if (mProcStateThrottle.contains(procState)) {
+                        if (DEBUG_COMPACTION) {
+                            Slog.d(TAG_AM, "Skipping full compaction for process " + name
+                                    + "; proc state is " + procState);
+                        }
+                        return;
+                    }
+
+                    long[] rssBefore = Process.getRss(pid);
+                    long anonRssBefore = rssBefore[2];
+
+                    if (rssBefore[0] == 0 && rssBefore[1] == 0 && rssBefore[2] == 0
+                            && rssBefore[3] == 0) {
+                        if (DEBUG_COMPACTION) {
+                            Slog.d(TAG_AM, "Skipping compaction for" + "process " + pid
+                                    + " with no memory usage. Dead?");
+                        }
+                        return;
+                    }
+
+                    if (action.equals(COMPACT_ACTION_FULL) || action.equals(COMPACT_ACTION_ANON)) {
+                        if (mFullAnonRssThrottleKb > 0L
+                                && anonRssBefore < mFullAnonRssThrottleKb) {
+                            if (DEBUG_COMPACTION) {
+                                Slog.d(TAG_AM, "Skipping full compaction for process "
+                                        + name + "; anon RSS is too small: " + anonRssBefore
+                                        + "KB.");
+                            }
+                            return;
+                        }
+
+                        if (lastCompactionStats != null && mFullDeltaRssThrottleKb > 0L) {
+                            long[] lastRss = lastCompactionStats.getRssAfterCompaction();
+                            long absDelta = Math.abs(rssBefore[1] - lastRss[1])
+                                    + Math.abs(rssBefore[2] - lastRss[2])
+                                    + Math.abs(rssBefore[3] - lastRss[3]);
+                            if (absDelta <= mFullDeltaRssThrottleKb) {
+                                if (DEBUG_COMPACTION) {
+                                    Slog.d(TAG_AM, "Skipping full compaction for process "
+                                            + name + "; abs delta is too small: " + absDelta
+                                            + "KB.");
+                                }
+                                return;
+                            }
+                        }
+                    }
+
+                    // Now we've passed through all the throttles and are going to compact, update
+                    // bookkeeping.
+                    switch (pendingAction) {
+                        case COMPACT_PROCESS_SOME:
+                            mSomeCompactionCount++;
+                            break;
+                        case COMPACT_PROCESS_FULL:
+                            mFullCompactionCount++;
+                            break;
+                        case COMPACT_PROCESS_PERSISTENT:
+                            mPersistentCompactionCount++;
+                            break;
+                        case COMPACT_PROCESS_BFGS:
+                            mBfgsCompactionCount++;
+                            break;
+                        default:
+                            break;
+                    }
+
                     try {
                         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Compact "
                                 + ((pendingAction == COMPACT_PROCESS_SOME) ? "some" : "full")
                                 + ": " + name);
                         long zramFreeKbBefore = Debug.getZramFreeKb();
-                        long[] rssBefore = Process.getRss(pid);
                         FileOutputStream fos = new FileOutputStream("/proc/" + pid + "/reclaim");
                         fos.write(action.getBytes());
                         fos.close();
@@ -485,9 +729,11 @@
                         long zramFreeKbAfter = Debug.getZramFreeKb();
                         EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, action,
                                 rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
-                                rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
-                                lastCompactAction, lastCompactTime, msg.arg1, msg.arg2,
-                                zramFreeKbBefore, zramFreeKbAfter);
+                                rssAfter[0] - rssBefore[0], rssAfter[1] - rssBefore[1],
+                                rssAfter[2] - rssBefore[2], rssAfter[3] - rssBefore[3], time,
+                                lastCompactAction, lastCompactTime, lastOomAdj, procState,
+                                zramFreeKbBefore, zramFreeKbAfter - zramFreeKbBefore);
+
                         // Note that as above not taking mPhenoTypeFlagLock here to avoid locking
                         // on every single compaction for a flag that will seldom change and the
                         // impact of reading the wrong value here is low.
@@ -495,17 +741,23 @@
                             StatsLog.write(StatsLog.APP_COMPACTED, pid, name, pendingAction,
                                     rssBefore[0], rssBefore[1], rssBefore[2], rssBefore[3],
                                     rssAfter[0], rssAfter[1], rssAfter[2], rssAfter[3], time,
-                                    lastCompactAction, lastCompactTime, msg.arg1,
-                                    ActivityManager.processStateAmToProto(msg.arg2),
+                                    lastCompactAction, lastCompactTime, lastOomAdj,
+                                    ActivityManager.processStateAmToProto(procState),
                                     zramFreeKbBefore, zramFreeKbAfter);
                         }
+
                         synchronized (mAm) {
                             proc.lastCompactTime = end;
                             proc.lastCompactAction = pendingAction;
                         }
-                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+                        if (action.equals(COMPACT_ACTION_FULL)
+                                || action.equals(COMPACT_ACTION_ANON)) {
+                            mLastCompactionStats.put(pid, new LastCompactionStats(rssAfter));
+                        }
                     } catch (Exception e) {
                         // nothing to do, presumably the process died
+                    } finally {
                         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     }
                     break;
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index a1c941e..fe95542 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -109,6 +109,14 @@
         clientPackageName = _clientPackageName;
     }
 
+    public boolean hasFlag(final int flag) {
+        return (flags & flag) != 0;
+    }
+
+    public boolean notHasFlag(final int flag) {
+        return (flags & flag) == 0;
+    }
+
     public void startAssociationIfNeeded() {
         // If we don't already have an active association, create one...  but only if this
         // is an association between two different processes.
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 4b12e43..e65a4e50 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -138,4 +138,4 @@
 30061 am_remove_task (Task ID|1|5), (Stack ID|1|5)
 
 # The task is being compacted
-30063 am_compact (Pid|1|5),(Process Name|3),(Action|3),(BeforeRssTotal|2|2),(BeforeRssFile|2|2),(BeforeRssAnon|2|2),(BeforeRssSwap|2|2),(AfterRssTotal|2|2),(AfterRssFile|2|2),(AfterRssAnon|2|2),(AfterRssSwap|2|2),(Time|2|3),(LastAction|1|2),(LastActionTimestamp|2|3),(setAdj|1|2),(procState|1|2),(BeforeZRAMFree|2|2),(AfterZRAMFree|2|2)
+30063 am_compact (Pid|1|5),(Process Name|3),(Action|3),(BeforeRssTotal|2|2),(BeforeRssFile|2|2),(BeforeRssAnon|2|2),(BeforeRssSwap|2|2),(DeltaRssTotal|2|2),(DeltaRssFile|2|2),(DeltaRssAnon|2|2),(DeltaRssSwap|2|2),(Time|2|3),(LastAction|1|2),(LastActionTimestamp|2|3),(setAdj|1|2),(procState|1|2),(BeforeZRAMFree|2|2),(DeltaZRAMFree|2|2)
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 924e331..5d47c9d 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -16,10 +16,19 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
 import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
 import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION;
+import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
+import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
 import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
+import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
 import static android.os.Process.SCHED_OTHER;
 import static android.os.Process.THREAD_GROUP_BG_NONINTERACTIVE;
 import static android.os.Process.THREAD_GROUP_DEFAULT;
@@ -413,7 +422,7 @@
                             app.kill("cached #" + numCached, true);
                         }
                         break;
-                    case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
+                    case PROCESS_STATE_CACHED_EMPTY:
                         if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
                                 && app.lastActivityTime < oldTime) {
                             app.kill("empty for "
@@ -718,7 +727,7 @@
         if (app.thread == null) {
             app.adjSeq = mAdjSeq;
             app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_BACKGROUND);
-            app.setCurProcState(ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+            app.setCurProcState(PROCESS_STATE_CACHED_EMPTY);
             app.curAdj = ProcessList.CACHED_APP_MAX_ADJ;
             app.setCurRawAdj(ProcessList.CACHED_APP_MAX_ADJ);
             app.completedAdjSeq = app.adjSeq;
@@ -773,7 +782,7 @@
                     app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
                 } else {
                     // screen off, restrict UI scheduling
-                    app.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+                    app.setCurProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
                     app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED);
                 }
             }
@@ -797,7 +806,7 @@
 
         boolean foregroundActivities = false;
         mTmpBroadcastQueue.clear();
-        if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) {
+        if (PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP && app == TOP_APP) {
             // The last app on the list is the foreground app.
             adj = ProcessList.FOREGROUND_APP_ADJ;
             schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
@@ -820,7 +829,7 @@
             adj = ProcessList.FOREGROUND_APP_ADJ;
             schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
             app.adjType = "instrumentation";
-            procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+            procState = PROCESS_STATE_FOREGROUND_SERVICE;
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making instrumentation: " + app);
             }
@@ -844,7 +853,7 @@
             schedGroup = app.execServicesFg ?
                     ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
             app.adjType = "exec-service";
-            procState = ActivityManager.PROCESS_STATE_SERVICE;
+            procState = PROCESS_STATE_SERVICE;
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making exec-service: " + app);
             }
@@ -864,7 +873,7 @@
             // At this point we don't actually know the adjustment.  Use the cached adj
             // value that the caller wants us to.
             adj = cachedAdj;
-            procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+            procState = PROCESS_STATE_CACHED_EMPTY;
             app.cached = true;
             app.empty = true;
             app.adjType = "cch-empty";
@@ -899,23 +908,28 @@
         }
 
         if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
-                || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION) {
+                || procState > PROCESS_STATE_FOREGROUND_SERVICE_LOCATION) {
             if (app.hasForegroundServices()) {
                 // The user is aware of this app, so make it visible.
                 adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                procState = app.hasLocationForegroundServices()
-                        ? ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION
-                        : ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+                if (app.hasLocationForegroundServices()) {
+                    procState = PROCESS_STATE_FOREGROUND_SERVICE_LOCATION;
+                    app.adjType = "fg-service-location";
+
+                } else {
+                    procState = PROCESS_STATE_FOREGROUND_SERVICE;
+                    app.adjType = "fg-service";
+                }
                 app.cached = false;
-                app.adjType = "fg-service";
                 schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to fg service: " + app);
+                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + app.adjType + ": "
+                            + app + " ");
                 }
             } else if (app.hasOverlayUi()) {
                 // The process is display an overlay UI.
                 adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                procState = PROCESS_STATE_IMPORTANT_FOREGROUND;
                 app.cached = false;
                 app.adjType = "has-overlay-ui";
                 schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
@@ -930,7 +944,7 @@
         // services so that it can finish performing any persistence/processing of in-memory state.
         if (app.hasForegroundServices() && adj > ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ
                 && (app.lastTopTime + mConstants.TOP_TO_FGS_GRACE_DURATION > now
-                || app.setProcState <= ActivityManager.PROCESS_STATE_TOP)) {
+                || app.setProcState <= PROCESS_STATE_TOP)) {
             adj = ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
             app.adjType = "fg-service-act";
             if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -939,13 +953,13 @@
         }
 
         if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
-                || procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
+                || procState > PROCESS_STATE_TRANSIENT_BACKGROUND) {
             if (app.forcingToImportant != null) {
                 // This is currently used for toasts...  they are not interactive, and
                 // we don't want them to cause the app to become fully foreground (and
                 // thus out of background check), so we yes the best background level we can.
                 adj = ProcessList.PERCEPTIBLE_APP_ADJ;
-                procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+                procState = PROCESS_STATE_TRANSIENT_BACKGROUND;
                 app.cached = false;
                 app.adjType = "force-imp";
                 app.adjSource = app.forcingToImportant;
@@ -1041,8 +1055,8 @@
             if (adj > ProcessList.BACKUP_APP_ADJ) {
                 if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app);
                 adj = ProcessList.BACKUP_APP_ADJ;
-                if (procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
-                    procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+                if (procState > PROCESS_STATE_TRANSIENT_BACKGROUND) {
+                    procState = PROCESS_STATE_TRANSIENT_BACKGROUND;
                 }
                 app.adjType = "backup";
                 if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
@@ -1059,21 +1073,16 @@
             }
         }
 
-        boolean mayBeTop = false;
-        String mayBeTopType = null;
-        Object mayBeTopSource = null;
-        Object mayBeTopTarget = null;
-
         for (int is = app.services.size() - 1;
                 is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                         || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
-                        || procState > ActivityManager.PROCESS_STATE_TOP);
+                        || procState > PROCESS_STATE_TOP);
                 is--) {
             ServiceRecord s = app.services.valueAt(is);
             if (s.startRequested) {
                 app.hasStartedServices = true;
-                if (procState > ActivityManager.PROCESS_STATE_SERVICE) {
-                    procState = ActivityManager.PROCESS_STATE_SERVICE;
+                if (procState > PROCESS_STATE_SERVICE) {
+                    procState = PROCESS_STATE_SERVICE;
                     app.adjType = "started-services";
                     if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                         reportOomAdjMessageLocked(TAG_OOM_ADJ,
@@ -1116,13 +1125,13 @@
             for (int conni = serviceConnections.size() - 1;
                     conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                             || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
-                            || procState > ActivityManager.PROCESS_STATE_TOP);
+                            || procState > PROCESS_STATE_TOP);
                     conni--) {
                 ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(conni);
                 for (int i = 0;
                         i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ
                                 || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
-                                || procState > ActivityManager.PROCESS_STATE_TOP);
+                                || procState > PROCESS_STATE_TOP);
                         i++) {
                     // XXX should compute this based on the max of
                     // all connected clients.
@@ -1148,7 +1157,7 @@
                             // If the other app is cached for any reason, for purposes here
                             // we are going to consider it empty.  The specific cached state
                             // doesn't propagate except under certain conditions.
-                            clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+                            clientProcState = PROCESS_STATE_CACHED_EMPTY;
                         }
                         String adjType = null;
                         if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
@@ -1219,6 +1228,7 @@
                                     newAdj = clientAdj;
                                 } else {
                                     if (adj > ProcessList.VISIBLE_APP_ADJ) {
+                                        // TODO: Is this too limiting for apps bound from TOP?
                                         newAdj = Math.max(clientAdj, ProcessList.VISIBLE_APP_ADJ);
                                     } else {
                                         newAdj = adj;
@@ -1247,55 +1257,50 @@
                                     schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
                                 }
                             }
-                            if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
-                                if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
-                                    // Special handling of clients who are in the top state.
-                                    // We *may* want to consider this process to be in the
-                                    // top state as well, but only if there is not another
-                                    // reason for it to be running.  Being on the top is a
-                                    // special state, meaning you are specifically running
-                                    // for the current top app.  If the process is already
-                                    // running in the background for some other reason, it
-                                    // is more important to continue considering it to be
-                                    // in the background state.
-                                    mayBeTop = true;
-                                    mayBeTopType = "service";
-                                    mayBeTopSource = cr.binding.client;
-                                    mayBeTopTarget = s.instanceName;
-                                    clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+                            if (clientProcState < PROCESS_STATE_TOP) {
+                                // Special handling for above-top states (persistent
+                                // processes).  These should not bring the current process
+                                // into the top state, since they are not on top.  Instead
+                                // give them the best bound state after that.
+                                if ((cr.flags & Context.BIND_FOREGROUND_SERVICE) != 0) {
+                                    clientProcState =
+                                            PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+                                } else if (mService.mWakefulness
+                                        == PowerManagerInternal.WAKEFULNESS_AWAKE
+                                        && (cr.flags & Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)
+                                                != 0) {
+                                    clientProcState =
+                                            PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
                                 } else {
-                                    // Special handling for above-top states (persistent
-                                    // processes).  These should not bring the current process
-                                    // into the top state, since they are not on top.  Instead
-                                    // give them the best state after that.
-                                    if ((cr.flags&Context.BIND_FOREGROUND_SERVICE) != 0) {
-                                        clientProcState =
-                                                ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                                    } else if (mService.mWakefulness
-                                            == PowerManagerInternal.WAKEFULNESS_AWAKE &&
-                                            (cr.flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)
-                                                    != 0) {
-                                        clientProcState =
-                                                ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                                    } else {
-                                        clientProcState =
-                                                ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
-                                    }
+                                    clientProcState =
+                                            PROCESS_STATE_IMPORTANT_FOREGROUND;
+                                }
+                            } else if (clientProcState == PROCESS_STATE_TOP) {
+                                if (cr.notHasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
+                                    // Go at most to BOUND_TOP, unless requested to elevate
+                                    // to client's state.
+                                    clientProcState = PROCESS_STATE_BOUND_TOP;
+                                }
+                            } else if (clientProcState
+                                    <= PROCESS_STATE_FOREGROUND_SERVICE) {
+                                if (cr.notHasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
+                                    clientProcState = PROCESS_STATE_FOREGROUND_SERVICE;
                                 }
                             }
                         } else if ((cr.flags & Context.BIND_IMPORTANT_BACKGROUND) == 0) {
                             if (clientProcState <
-                                    ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
+                                    PROCESS_STATE_TRANSIENT_BACKGROUND) {
                                 clientProcState =
-                                        ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+                                        PROCESS_STATE_TRANSIENT_BACKGROUND;
                             }
                         } else {
                             if (clientProcState <
-                                    ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
+                                    PROCESS_STATE_IMPORTANT_BACKGROUND) {
                                 clientProcState =
-                                        ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
+                                        PROCESS_STATE_IMPORTANT_BACKGROUND;
                             }
                         }
+
                         if (schedGroup < ProcessList.SCHED_GROUP_TOP_APP
                                 && (cr.flags & Context.BIND_SCHEDULE_LIKE_TOP_APP) != 0) {
                             schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
@@ -1304,6 +1309,7 @@
                         if (!trackedProcState) {
                             cr.trackProcState(clientProcState, mAdjSeq, now);
                         }
+
                         if (procState > clientProcState) {
                             procState = clientProcState;
                             app.setCurRawProcState(procState);
@@ -1311,7 +1317,7 @@
                                 adjType = "service";
                             }
                         }
-                        if (procState < ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+                        if (procState < PROCESS_STATE_IMPORTANT_BACKGROUND
                                 && (cr.flags & Context.BIND_SHOWING_UI) != 0) {
                             app.setPendingUiClean(true);
                         }
@@ -1366,13 +1372,13 @@
         for (int provi = app.pubProviders.size() - 1;
                 provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                         || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
-                        || procState > ActivityManager.PROCESS_STATE_TOP);
+                        || procState > PROCESS_STATE_TOP);
                 provi--) {
             ContentProviderRecord cpr = app.pubProviders.valueAt(provi);
             for (int i = cpr.connections.size() - 1;
                     i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                             || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
-                            || procState > ActivityManager.PROCESS_STATE_TOP);
+                            || procState > PROCESS_STATE_TOP);
                     i--) {
                 ContentProviderConnection conn = cpr.connections.get(i);
                 ProcessRecord client = conn.client;
@@ -1392,7 +1398,7 @@
                 if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
                     // If the other app is cached for any reason, for purposes here
                     // we are going to consider it empty.
-                    clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+                    clientProcState = PROCESS_STATE_CACHED_EMPTY;
                 }
                 String adjType = null;
                 if (adj > clientAdj) {
@@ -1407,34 +1413,18 @@
                     }
                     app.cached &= client.cached;
                 }
-                if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
-                    if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
-                        // Special handling of clients who are in the top state.
-                        // We *may* want to consider this process to be in the
-                        // top state as well, but only if there is not another
-                        // reason for it to be running.  Being on the top is a
-                        // special state, meaning you are specifically running
-                        // for the current top app.  If the process is already
-                        // running in the background for some other reason, it
-                        // is more important to continue considering it to be
-                        // in the background state.
-                        mayBeTop = true;
-                        clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
-                        mayBeTopType = adjType = "provider-top";
-                        mayBeTopSource = client;
-                        mayBeTopTarget = cpr.name;
+
+                if (clientProcState <= PROCESS_STATE_FOREGROUND_SERVICE) {
+                    if (adjType == null) {
+                        adjType = "provider";
+                    }
+                    if (clientProcState == PROCESS_STATE_TOP) {
+                        clientProcState = PROCESS_STATE_BOUND_TOP;
                     } else {
-                        // Special handling for above-top states (persistent
-                        // processes).  These should not bring the current process
-                        // into the top state, since they are not on top.  Instead
-                        // give them the best state after that.
-                        clientProcState =
-                                ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                        if (adjType == null) {
-                            adjType = "provider";
-                        }
+                        clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
                     }
                 }
+
                 conn.trackProcState(clientProcState, mAdjSeq, now);
                 if (procState > clientProcState) {
                     procState = clientProcState;
@@ -1474,8 +1464,8 @@
                                 "Raise adj to external provider: " + app);
                     }
                 }
-                if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                    procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                if (procState > PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                    procState = PROCESS_STATE_IMPORTANT_FOREGROUND;
                     app.setCurRawProcState(procState);
                     if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                         reportOomAdjMessageLocked(TAG_OOM_ADJ,
@@ -1507,53 +1497,7 @@
             }
         }
 
-        if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) {
-            // A client of one of our services or providers is in the top state.  We
-            // *may* want to be in the top state, but not if we are already running in
-            // the background for some other reason.  For the decision here, we are going
-            // to pick out a few specific states that we want to remain in when a client
-            // is top (states that tend to be longer-term) and otherwise allow it to go
-            // to the top state.
-            switch (procState) {
-                case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
-                case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
-                case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION:
-                    // Something else is keeping it at this level, just leave it.
-                    break;
-                case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
-                case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND:
-                case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND:
-                case ActivityManager.PROCESS_STATE_SERVICE:
-                    // These all are longer-term states, so pull them up to the top
-                    // of the background states, but not all the way to the top state.
-                    procState = ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-                    app.adjType = mayBeTopType;
-                    app.adjSource = mayBeTopSource;
-                    app.adjTarget = mayBeTopTarget;
-                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                        reportOomAdjMessageLocked(TAG_OOM_ADJ, "May be top raise to " + mayBeTopType
-                                + ": " + app + ", due to " + mayBeTopSource
-                                + " adj=" + adj + " procState="
-                                + ProcessList.makeProcStateString(procState));
-                    }
-                    break;
-                default:
-                    // Otherwise, top is a better choice, so take it.
-                    procState = ActivityManager.PROCESS_STATE_TOP;
-                    app.adjType = mayBeTopType;
-                    app.adjSource = mayBeTopSource;
-                    app.adjTarget = mayBeTopTarget;
-                    if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
-                        reportOomAdjMessageLocked(TAG_OOM_ADJ, "May be top raise to " + mayBeTopType
-                                + ": " + app + ", due to " + mayBeTopSource
-                                + " adj=" + adj + " procState="
-                                + ProcessList.makeProcStateString(procState));
-                    }
-                    break;
-            }
-        }
-
-        if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
+        if (procState >= PROCESS_STATE_CACHED_EMPTY) {
             if (app.hasClientActivities()) {
                 // This is a cached process, but with client activities.  Mark it so.
                 procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
@@ -1607,8 +1551,8 @@
 
         // Put bound foreground services in a special sched group for additional
         // restrictions on screen off
-        if (procState >= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE &&
-                mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
+        if (procState >= PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+                && mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
             if (schedGroup > ProcessList.SCHED_GROUP_RESTRICTED) {
                 schedGroup = ProcessList.SCHED_GROUP_RESTRICTED;
             }
@@ -1905,8 +1849,8 @@
                         + " (" + app.getCurProcState() + ")" + ": " + app.adjType;
                 reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
             }
-            boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE;
-            boolean curImportant = app.getCurProcState() < ActivityManager.PROCESS_STATE_SERVICE;
+            boolean setImportant = app.setProcState < PROCESS_STATE_SERVICE;
+            boolean curImportant = app.getCurProcState() < PROCESS_STATE_SERVICE;
             if (setImportant && !curImportant) {
                 // This app is no longer something we consider important enough to allow to use
                 // arbitrary amounts of battery power. Note its current CPU time to later know to
@@ -1969,10 +1913,11 @@
         // To avoid some abuse patterns, we are going to be careful about what we consider
         // to be an app interaction.  Being the top activity doesn't count while the display
         // is sleeping, nor do short foreground services.
-        if (app.getCurProcState() <= ActivityManager.PROCESS_STATE_TOP) {
+        if (app.getCurProcState() <= PROCESS_STATE_TOP
+                || app.getCurProcState() == PROCESS_STATE_BOUND_TOP) {
             isInteraction = true;
             app.setFgInteractionTime(0);
-        } else if (app.getCurProcState() <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+        } else if (app.getCurProcState() <= PROCESS_STATE_FOREGROUND_SERVICE) {
             if (app.getFgInteractionTime() == 0) {
                 app.setFgInteractionTime(nowElapsed);
                 isInteraction = false;
@@ -1982,7 +1927,7 @@
             }
         } else {
             isInteraction =
-                    app.getCurProcState() <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                    app.getCurProcState() <= PROCESS_STATE_IMPORTANT_FOREGROUND;
             app.setFgInteractionTime(0);
         }
         if (isInteraction
@@ -2004,8 +1949,8 @@
     }
 
     private void maybeUpdateLastTopTime(ProcessRecord app, long nowUptime) {
-        if (app.setProcState <= ActivityManager.PROCESS_STATE_TOP
-                && app.getCurProcState() > ActivityManager.PROCESS_STATE_TOP) {
+        if (app.setProcState <= PROCESS_STATE_TOP
+                && app.getCurProcState() > PROCESS_STATE_TOP) {
             app.lastTopTime = nowUptime;
         }
     }
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index d02fd73..f1f40d4 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -769,6 +769,9 @@
             case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION:
                 procState = "FGSL";
                 break;
+            case ActivityManager.PROCESS_STATE_BOUND_TOP:
+                procState = "BTOP";
+                break;
             case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
                 procState = "FGS ";
                 break;
@@ -836,6 +839,9 @@
             case ActivityManager.PROCESS_STATE_TOP:
                 return AppProtoEnums.PROCESS_STATE_TOP;
             case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION:
+                return AppProtoEnums.PROCESS_STATE_FOREGROUND_SERVICE;
+            case ActivityManager.PROCESS_STATE_BOUND_TOP:
+                return AppProtoEnums.PROCESS_STATE_BOUND_TOP;
             case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
                 return AppProtoEnums.PROCESS_STATE_FOREGROUND_SERVICE;
             case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
@@ -966,6 +972,7 @@
         PROC_MEM_TOP,                   // ActivityManager.PROCESS_STATE_TOP
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+        PROC_MEM_TOP,                   // ActivityManager.PROCESS_STATE_BOUND_TOP
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index cc4116e..2bd9198 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -241,6 +241,20 @@
 
     volatile boolean mBootCompleted;
 
+    /**
+     * In this mode, user is always stopped when switched out but locking of user data is
+     * postponed until total number of unlocked users in the system reaches mMaxRunningUsers.
+     * Once total number of unlocked users reach mMaxRunningUsers, least recentely used user
+     * will be locked.
+     */
+    boolean mDelayUserDataLocking;
+    /**
+     * Keep track of last active users for mDelayUserDataLocking.
+     * The latest stopped user is placed in front while the least recently stopped user in back.
+     */
+    @GuardedBy("mLock")
+    private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>();
+
     UserController(ActivityManagerService service) {
         this(new Injector(service));
     }
@@ -738,7 +752,9 @@
     void finishUserStopped(UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
         final boolean stopped;
+        boolean lockUser = true;
         ArrayList<IStopUserCallback> callbacks;
+        int userIdToLock = userId;
         synchronized (mLock) {
             callbacks = new ArrayList<>(uss.mStopCallbacks);
             if (mStartedUsers.get(userId) != uss || uss.state != UserState.STATE_SHUTDOWN) {
@@ -749,9 +765,12 @@
                 mStartedUsers.remove(userId);
                 mUserLru.remove(Integer.valueOf(userId));
                 updateStartedUserArrayLU();
+                userIdToLock = updateUserToLockLU(userId);
+                if (userIdToLock == UserHandle.USER_NULL) {
+                    lockUser = false;
+                }
             }
         }
-
         if (stopped) {
             mInjector.getUserManagerInternal().removeUserState(userId);
             mInjector.activityManagerOnUserStopped(userId);
@@ -776,18 +795,22 @@
                 mInjector.getUserManager().removeUserEvenWhenDisallowed(userId);
             }
 
+            if (!lockUser) {
+                return;
+            }
+            final int userIdToLockF = userIdToLock;
             // Evict the user's credential encryption key. Performed on FgThread to make it
             // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking
             // to prevent data corruption.
             FgThread.getHandler().post(() -> {
                 synchronized (mLock) {
-                    if (mStartedUsers.get(userId) != null) {
+                    if (mStartedUsers.get(userIdToLockF) != null) {
                         Slog.w(TAG, "User was restarted, skipping key eviction");
                         return;
                     }
                 }
                 try {
-                    getStorageManager().lockUserKey(userId);
+                    mInjector.getStorageManager().lockUserKey(userIdToLockF);
                 } catch (RemoteException re) {
                     throw re.rethrowAsRuntimeException();
                 }
@@ -796,6 +819,39 @@
     }
 
     /**
+     * For mDelayUserDataLocking mode, storage once unlocked is kept unlocked.
+     * Total number of unlocked user storage is limited by mMaxRunningUsers.
+     * If there are more unlocked users, evict and lock the least recently stopped user and
+     * lock that user's data. Regardless of the mode, ephemeral user is always locked
+     * immediately.
+     *
+     * @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked.
+     */
+    @GuardedBy("mLock")
+    private int updateUserToLockLU(int userId) {
+        int userIdToLock = userId;
+        if (mDelayUserDataLocking && !getUserInfo(userId).isEphemeral()
+                && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
+            mLastActiveUsers.remove((Integer) userId); // arg should be object, not index
+            mLastActiveUsers.add(0, userId);
+            int totalUnlockedUsers = mStartedUsers.size() + mLastActiveUsers.size();
+            if (totalUnlockedUsers > mMaxRunningUsers) { // should lock a user
+                userIdToLock = mLastActiveUsers.get(mLastActiveUsers.size() - 1);
+                mLastActiveUsers.remove(mLastActiveUsers.size() - 1);
+                Slog.i(TAG, "finishUserStopped, stopping user:" + userId
+                        + " lock user:" + userIdToLock);
+            } else {
+                Slog.i(TAG, "finishUserStopped, user:" + userId
+                        + ",skip locking");
+                // do not lock
+                userIdToLock = UserHandle.USER_NULL;
+
+            }
+        }
+        return userIdToLock;
+    }
+
+    /**
      * Determines the list of users that should be stopped together with the specified
      * {@code userId}. The returned list includes {@code userId}.
      */
@@ -896,9 +952,6 @@
         }
     }
 
-    private IStorageManager getStorageManager() {
-        return IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
-    }
     boolean startUser(final int userId, final boolean foreground) {
         return startUser(userId, foreground, null);
     }
@@ -1199,7 +1252,7 @@
         UserState uss;
         if (!StorageManager.isUserKeyUnlocked(userId)) {
             final UserInfo userInfo = getUserInfo(userId);
-            final IStorageManager storageManager = getStorageManager();
+            final IStorageManager storageManager = mInjector.getStorageManager();
             try {
                 // We always want to unlock user storage, even user is not started yet
                 storageManager.unlockUserKey(userId, userInfo.serialNumber, token, secret);
@@ -1334,9 +1387,9 @@
         if (oldUserId == UserHandle.USER_SYSTEM) {
             return;
         }
-        // For now, only check for user restriction. Additional checks can be added here
+        // If running in background is disabled or mDelayUserDataLocking mode, stop the user.
         boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND,
-                oldUserId);
+                oldUserId) || mDelayUserDataLocking;
         if (!disallowRunInBg) {
             return;
         }
@@ -2033,6 +2086,8 @@
                     pw.println(mUserProfileGroupIds.valueAt(i));
                 }
             }
+            pw.println("  mCurrentUserId:" + mCurrentUserId);
+            pw.println("  mLastActiveUsers:" + mLastActiveUsers);
         }
     }
 
@@ -2326,5 +2381,9 @@
         protected boolean isCallerRecents(int callingUid) {
             return mService.mAtmInternal.isCallerRecents(callingUid);
         }
+
+        protected IStorageManager getStorageManager() {
+            return IStorageManager.Stub.asInterface(ServiceManager.getService("mount"));
+        }
     }
 }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 2e5dd3b..c9e7cfa 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -153,6 +153,7 @@
         UID_STATE_TOP,                  // ActivityManager.PROCESS_STATE_TOP
         UID_STATE_FOREGROUND_SERVICE_LOCATION,
                                         // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE_LOCATION
+        UID_STATE_FOREGROUND,           // ActivityManager.PROCESS_STATE_BOUND_TOP
         UID_STATE_FOREGROUND_SERVICE,   // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
         UID_STATE_FOREGROUND,           // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         UID_STATE_FOREGROUND,           // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java
index 1681c5b..bc78d1a 100644
--- a/services/core/java/com/android/server/attention/AttentionManagerService.java
+++ b/services/core/java/com/android/server/attention/AttentionManagerService.java
@@ -19,6 +19,7 @@
 import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
 import static android.provider.Settings.System.ADAPTIVE_SLEEP;
 import static android.service.attention.AttentionService.ATTENTION_FAILURE_CANCELLED;
+import static android.service.attention.AttentionService.ATTENTION_FAILURE_UNKNOWN;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -249,6 +250,7 @@
                 if (userState.mPendingAttentionCheck != null
                         && userState.mPendingAttentionCheck.mCallbackInternal.equals(
                         callbackInternal)) {
+                    userState.mPendingAttentionCheck.cancel(ATTENTION_FAILURE_UNKNOWN);
                     userState.mPendingAttentionCheck = null;
                 }
                 return;
@@ -624,7 +626,7 @@
             if (userState == null) {
                 return;
             }
-            cancel(userState, AttentionService.ATTENTION_FAILURE_UNKNOWN);
+            cancel(userState, ATTENTION_FAILURE_UNKNOWN);
 
             mContext.unbindService(userState.mConnection);
             userState.mConnection.cleanupService();
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 5f624ba..7750bfe 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -207,7 +207,7 @@
                     mDeviceBroker.setDeviceVolume(
                             streamState, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
                 }
-                makeA2dpDeviceAvailable(address, btDevice.getName(),
+                makeA2dpDeviceAvailable(address, BtHelper.getName(btDevice),
                         "onSetA2dpSinkConnectionState", a2dpCodec);
             }
         }
@@ -257,7 +257,7 @@
             if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
                 makeHearingAidDeviceUnavailable(address);
             } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
-                makeHearingAidDeviceAvailable(address, btDevice.getName(),
+                makeHearingAidDeviceAvailable(address, BtHelper.getName(btDevice),
                         "onSetHearingAidConnectionState");
             }
         }
@@ -318,7 +318,7 @@
                 }
             }
             if (AudioSystem.handleDeviceConfigChange(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
-                    btDevice.getName(), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
+                    BtHelper.getName(btDevice), a2dpCodec) != AudioSystem.AUDIO_STATUS_OK) {
                 int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
                 // force A2DP device disconnection in case of error so that AudioService state is
                 // consistent with audio policy manager state
@@ -603,10 +603,9 @@
                 }
                 // A2DP device exists, handle active device change
                 final String existingDevicekey = mConnectedDevices.keyAt(i);
-                final String deviceName = device.getName();
                 mConnectedDevices.remove(existingDevicekey);
                 mConnectedDevices.put(deviceKey, new DeviceInfo(
-                        AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, deviceName,
+                        AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, BtHelper.getName(device),
                         address, a2dpCodec));
                 mDeviceBroker.postA2dpActiveDeviceChange(
                         new BtHelper.BluetoothA2dpDeviceInfo(
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 522a55d..2d9156b 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -152,6 +152,14 @@
         }
     }
 
+    /*package*/ @NonNull static String getName(@NonNull BluetoothDevice device) {
+        final String deviceName = device.getName();
+        if (deviceName == null) {
+            return "";
+        }
+        return deviceName;
+    }
+
     //----------------------------------------------------------------------
     // Interface for AudioDeviceBroker
 
@@ -515,7 +523,7 @@
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             address = "";
         }
-        String btDeviceName =  btDevice.getName();
+        String btDeviceName =  getName(btDevice);
         boolean result = false;
         if (isActive) {
             result |= mDeviceBroker.handleDeviceConnection(
diff --git a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
index c60dd6c..d8e7b7d 100644
--- a/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
+++ b/services/core/java/com/android/server/biometrics/BiometricServiceBase.java
@@ -657,6 +657,7 @@
         Slog.e(getTag(), "HAL died");
         mMetricsLogger.count(getMetrics().tagHalDied(), 1);
         mHALDeathCount++;
+        mCurrentUserId = UserHandle.USER_NULL;
         handleError(getHalDeviceId(), BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
                 0 /*vendorCode */);
 
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index fe762c0..c573bbb 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -46,9 +46,9 @@
 import android.os.SELinux;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.service.restricted_image.RestrictedImagesDumpProto;
 import android.service.restricted_image.RestrictedImageProto;
 import android.service.restricted_image.RestrictedImageSetProto;
+import android.service.restricted_image.RestrictedImagesDumpProto;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
@@ -383,6 +383,12 @@
         @Override // Binder call
         public void resetLockout(byte[] token) {
             checkPermission(MANAGE_BIOMETRIC);
+
+            if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) {
+                Slog.w(TAG, "Ignoring lockout reset, no templates enrolled");
+                return;
+            }
+
             try {
                 mDaemonWrapper.resetLockout(token);
             } catch (RemoteException e) {
@@ -391,61 +397,62 @@
         }
 
         @Override
-        public boolean setFeature(int feature, boolean enabled, final byte[] token) {
+        public void setFeature(int feature, boolean enabled, final byte[] token,
+                IFaceServiceReceiver receiver) {
             checkPermission(MANAGE_BIOMETRIC);
 
-            if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) {
-                Slog.e(TAG, "No enrolled biometrics while setting feature: " + feature);
-                return false;
-            }
-
-            final ArrayList<Byte> byteToken = new ArrayList<>();
-            for (int i = 0; i < token.length; i++) {
-                byteToken.add(token[i]);
-            }
-
-            // TODO: Support multiple faces
-            final int faceId = getFirstTemplateForUser(mCurrentUserId);
-
-            if (mDaemon != null) {
-                try {
-                    return mDaemon.setFeature(feature, enabled, byteToken, faceId) == Status.OK;
-                } catch (RemoteException e) {
-                    Slog.e(getTag(), "Unable to set feature: " + feature + " to enabled:" + enabled,
-                            e);
+            mHandler.post(() -> {
+                if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) {
+                    Slog.e(TAG, "No enrolled biometrics while setting feature: " + feature);
+                    return;
                 }
-            }
-            return false;
+
+                final ArrayList<Byte> byteToken = new ArrayList<>();
+                for (int i = 0; i < token.length; i++) {
+                    byteToken.add(token[i]);
+                }
+
+                // TODO: Support multiple faces
+                final int faceId = getFirstTemplateForUser(mCurrentUserId);
+
+                if (mDaemon != null) {
+                    try {
+                        final int result = mDaemon.setFeature(feature, enabled, byteToken, faceId);
+                        receiver.onFeatureSet(result == Status.OK, feature);
+                    } catch (RemoteException e) {
+                        Slog.e(getTag(), "Unable to set feature: " + feature
+                                        + " to enabled:" + enabled, e);
+                    }
+                }
+            });
+
         }
 
         @Override
-        public boolean getFeature(int feature) {
+        public void getFeature(int feature, IFaceServiceReceiver receiver) {
             checkPermission(MANAGE_BIOMETRIC);
 
-            // This should ideally return tri-state, but the user isn't shown settings unless
-            // they are enrolled so it's fine for now.
-            if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) {
-                Slog.e(TAG, "No enrolled biometrics while getting feature: " + feature);
-                return false;
-            }
-
-            // TODO: Support multiple faces
-            final int faceId = getFirstTemplateForUser(mCurrentUserId);
-
-            if (mDaemon != null) {
-                try {
-                    OptionalBool result = mDaemon.getFeature(feature, faceId);
-                    if (result.status == Status.OK) {
-                        return result.value;
-                    } else {
-                        // Same tri-state comment applies here.
-                        return false;
-                    }
-                } catch (RemoteException e) {
-                    Slog.e(getTag(), "Unable to getRequireAttention", e);
+            mHandler.post(() -> {
+                // This should ideally return tri-state, but the user isn't shown settings unless
+                // they are enrolled so it's fine for now.
+                if (!FaceService.this.hasEnrolledBiometrics(mCurrentUserId)) {
+                    Slog.e(TAG, "No enrolled biometrics while getting feature: " + feature);
+                    return;
                 }
-            }
-            return false;
+
+                // TODO: Support multiple faces
+                final int faceId = getFirstTemplateForUser(mCurrentUserId);
+
+                if (mDaemon != null) {
+                    try {
+                        OptionalBool result = mDaemon.getFeature(feature, faceId);
+                        receiver.onFeatureGet(result.status == Status.OK, feature, result.value);
+                    } catch (RemoteException e) {
+                        Slog.e(getTag(), "Unable to getRequireAttention", e);
+                    }
+                }
+            });
+
         }
 
         @Override
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 164468e..3d9a47b 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -419,6 +419,12 @@
         @Override // Binder call
         public void resetTimeout(byte [] token) {
             checkPermission(RESET_FINGERPRINT_LOCKOUT);
+
+            if (!FingerprintService.this.hasEnrolledBiometrics(mCurrentUserId)) {
+                Slog.w(TAG, "Ignoring lockout reset, no templates enrolled");
+                return;
+            }
+
             // TODO: confirm security token when we move timeout management into the HAL layer.
             mHandler.post(mResetFailedAttemptsForCurrentUserRunnable);
         }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
index 0bbaf25..5307697 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java
@@ -90,7 +90,11 @@
             for (ModuleWatcher watcher : mModuleWatchers) {
                 combined.addAll(watcher.currentList);
             }
-            TunerCallback.dispatch(() -> mListener.onListUpdated(combined));
+            try {
+                mListener.onListUpdated(combined);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "mListener.onListUpdated() failed: ", ex);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
index 2df8982..5e79c59 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java
@@ -54,13 +54,6 @@
     @GuardedBy("mLock")
     private final Map<Integer, RadioModule> mModules = new HashMap<>();
 
-    // Map from module ID to TunerSession created by openSession().
-    //
-    // Because this service currently implements a 1 AIDL to 1 HAL policy, mTunerSessions is used to
-    // enforce the "aggresive open" policy mandated for IBroadcastRadio.openSession(). In the
-    // future, this solution will be replaced with a multiple-AIDL to 1 HAL implementation.
-    private final Map<Integer, TunerSession> mTunerSessions = new HashMap<>();
-
     private IServiceNotification.Stub mServiceListener = new IServiceNotification.Stub() {
         @Override
         public void onRegistration(String fqName, String serviceName, boolean preexisting) {
@@ -81,8 +74,10 @@
                 }
                 Slog.v(TAG, "loaded broadcast radio module " + moduleId + ": " + serviceName
                         + " (HAL 2.0)");
-                closeTunerSessionLocked(moduleId);
-                mModules.put(moduleId, module);
+                RadioModule prevModule = mModules.put(moduleId, module);
+                if (prevModule != null) {
+                    prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE);
+                }
 
                 if (newService) {
                     mServiceNameToModuleIdMap.put(serviceName, moduleId);
@@ -105,8 +100,10 @@
             Slog.v(TAG, "serviceDied(" + cookie + ")");
             synchronized (mLock) {
                 int moduleId = (int) cookie;
-                mModules.remove(moduleId);
-                closeTunerSessionLocked(moduleId);
+                RadioModule prevModule = mModules.remove(moduleId);
+                if (prevModule != null) {
+                    prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE);
+                }
 
                 for (Map.Entry<String, Integer> entry : mServiceNameToModuleIdMap.entrySet()) {
                     if (entry.getValue() == moduleId) {
@@ -166,13 +163,9 @@
             if (module == null) {
                 throw new IllegalArgumentException("Invalid module ID");
             }
-            closeTunerSessionLocked(moduleId);
         }
 
         TunerSession tunerSession = module.openSession(callback);
-        synchronized (mLock) {
-            mTunerSessions.put(moduleId, tunerSession);
-        }
         if (legacyConfig != null) {
             tunerSession.setConfiguration(legacyConfig);
         }
@@ -198,12 +191,4 @@
         }
         return aggregator;
     }
-
-    private void closeTunerSessionLocked(int moduleId) {
-        TunerSession tunerSession = mTunerSessions.remove(moduleId);
-        if (tunerSession != null) {
-            Slog.d(TAG, "Closing previous TunerSession");
-            tunerSession.close(RadioTuner.ERROR_HARDWARE_FAILURE);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
index 832f8e1..acb0207 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java
@@ -26,16 +26,26 @@
 import android.hardware.broadcastradio.V2_0.IAnnouncementListener;
 import android.hardware.broadcastradio.V2_0.IBroadcastRadio;
 import android.hardware.broadcastradio.V2_0.ICloseHandle;
+import android.hardware.broadcastradio.V2_0.ITunerCallback;
 import android.hardware.broadcastradio.V2_0.ITunerSession;
+import android.hardware.broadcastradio.V2_0.ProgramInfo;
+import android.hardware.broadcastradio.V2_0.ProgramListChunk;
+import android.hardware.broadcastradio.V2_0.ProgramSelector;
 import android.hardware.broadcastradio.V2_0.Result;
+import android.hardware.broadcastradio.V2_0.VendorKeyValue;
 import android.hardware.radio.RadioManager;
+import android.os.DeadObjectException;
 import android.os.RemoteException;
 import android.util.MutableInt;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 class RadioModule {
@@ -44,8 +54,63 @@
     @NonNull private final IBroadcastRadio mService;
     @NonNull public final RadioManager.ModuleProperties mProperties;
 
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private ITunerSession mHalTunerSession;
+
+    // Tracks antenna state reported by HAL (if any).
+    @GuardedBy("mLock")
+    private Boolean mAntennaConnected = null;
+
+    @GuardedBy("mLock")
+    private RadioManager.ProgramInfo mProgramInfo = null;
+
+    // Callback registered with the HAL to relay callbacks to AIDL clients.
+    private final ITunerCallback mHalTunerCallback = new ITunerCallback.Stub() {
+        @Override
+        public void onTuneFailed(int result, ProgramSelector programSelector) {
+            fanoutAidlCallback(cb -> cb.onTuneFailed(result, Convert.programSelectorFromHal(
+                    programSelector)));
+        }
+
+        @Override
+        public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) {
+            RadioManager.ProgramInfo programInfo = Convert.programInfoFromHal(halProgramInfo);
+            synchronized (mLock) {
+                mProgramInfo = programInfo;
+                fanoutAidlCallbackLocked(cb -> cb.onCurrentProgramInfoChanged(programInfo));
+            }
+        }
+
+        @Override
+        public void onProgramListUpdated(ProgramListChunk programListChunk) {
+            // TODO: Cache per-AIDL client filters, send union of filters to HAL, use filters to fan
+            // back out to clients.
+            fanoutAidlCallback(cb -> cb.onProgramListUpdated(Convert.programListChunkFromHal(
+                    programListChunk)));
+        }
+
+        @Override
+        public void onAntennaStateChange(boolean connected) {
+            synchronized (mLock) {
+                mAntennaConnected = connected;
+                fanoutAidlCallbackLocked(cb -> cb.onAntennaState(connected));
+            }
+        }
+
+        @Override
+        public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {
+            fanoutAidlCallback(cb -> cb.onParametersUpdated(Convert.vendorInfoFromHal(parameters)));
+        }
+    };
+
+    // Collection of active AIDL tuner sessions created through openSession().
+    @GuardedBy("mLock")
+    private final Set<TunerSession> mAidlTunerSessions = new HashSet<>();
+
     private RadioModule(@NonNull IBroadcastRadio service,
-            @NonNull RadioManager.ModuleProperties properties) {
+            @NonNull RadioManager.ModuleProperties properties) throws RemoteException {
         mProperties = Objects.requireNonNull(properties);
         mService = Objects.requireNonNull(service);
     }
@@ -81,21 +146,85 @@
 
     public @NonNull TunerSession openSession(@NonNull android.hardware.radio.ITunerCallback userCb)
             throws RemoteException {
-        TunerCallback cb = new TunerCallback(Objects.requireNonNull(userCb));
-        Mutable<ITunerSession> hwSession = new Mutable<>();
-        MutableInt halResult = new MutableInt(Result.UNKNOWN_ERROR);
+        synchronized (mLock) {
+            if (mHalTunerSession == null) {
+                Mutable<ITunerSession> hwSession = new Mutable<>();
+                mService.openSession(mHalTunerCallback, (result, session) -> {
+                    Convert.throwOnError("openSession", result);
+                    hwSession.value = session;
+                });
+                mHalTunerSession = Objects.requireNonNull(hwSession.value);
+            }
+            TunerSession tunerSession = new TunerSession(this, mHalTunerSession, userCb);
+            mAidlTunerSessions.add(tunerSession);
 
-        synchronized (mService) {
-            mService.openSession(cb, (result, session) -> {
-                hwSession.value = session;
-                halResult.value = result;
-            });
+            // Propagate state to new client. Note: These callbacks are invoked while holding mLock
+            // to prevent race conditions with new callbacks from the HAL.
+            if (mAntennaConnected != null) {
+                userCb.onAntennaState(mAntennaConnected);
+            }
+            if (mProgramInfo != null) {
+                userCb.onCurrentProgramInfoChanged(mProgramInfo);
+            }
+
+            return tunerSession;
         }
+    }
 
-        Convert.throwOnError("openSession", halResult.value);
-        Objects.requireNonNull(hwSession.value);
+    public void closeSessions(Integer error) {
+        // Copy the contents of mAidlTunerSessions into a local array because TunerSession.close()
+        // must be called without mAidlTunerSessions locked because it can call
+        // onTunerSessionClosed().
+        TunerSession[] tunerSessions;
+        synchronized (mLock) {
+            tunerSessions = new TunerSession[mAidlTunerSessions.size()];
+            mAidlTunerSessions.toArray(tunerSessions);
+            mAidlTunerSessions.clear();
+        }
+        for (TunerSession tunerSession : tunerSessions) {
+            tunerSession.close(error);
+        }
+    }
 
-        return new TunerSession(this, hwSession.value, cb);
+    void onTunerSessionClosed(TunerSession tunerSession) {
+        synchronized (mLock) {
+            mAidlTunerSessions.remove(tunerSession);
+            if (mAidlTunerSessions.isEmpty() && mHalTunerSession != null) {
+                Slog.v(TAG, "closing HAL tuner session");
+                try {
+                    mHalTunerSession.close();
+                } catch (RemoteException ex) {
+                    Slog.e(TAG, "mHalTunerSession.close() failed: ", ex);
+                }
+                mHalTunerSession = null;
+            }
+        }
+    }
+
+    interface AidlCallbackRunnable {
+        void run(android.hardware.radio.ITunerCallback callback) throws RemoteException;
+    }
+
+    // Invokes runnable with each TunerSession currently open.
+    void fanoutAidlCallback(AidlCallbackRunnable runnable) {
+        synchronized (mLock) {
+            fanoutAidlCallbackLocked(runnable);
+        }
+    }
+
+    private void fanoutAidlCallbackLocked(AidlCallbackRunnable runnable) {
+        for (TunerSession tunerSession : mAidlTunerSessions) {
+            try {
+                runnable.run(tunerSession.mCallback);
+            } catch (DeadObjectException ex) {
+                // The other side died without calling close(), so just purge it from our
+                // records.
+                Slog.e(TAG, "Removing dead TunerSession");
+                mAidlTunerSessions.remove(tunerSession);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to invoke ITunerCallback: ", ex);
+            }
+        }
     }
 
     public android.hardware.radio.ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes,
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
deleted file mode 100644
index 3c4b49c..0000000
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerCallback.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/**
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.broadcastradio.hal2;
-
-import android.annotation.NonNull;
-import android.hardware.broadcastradio.V2_0.ITunerCallback;
-import android.hardware.broadcastradio.V2_0.ProgramInfo;
-import android.hardware.broadcastradio.V2_0.ProgramListChunk;
-import android.hardware.broadcastradio.V2_0.ProgramSelector;
-import android.hardware.broadcastradio.V2_0.VendorKeyValue;
-import android.os.RemoteException;
-import android.util.Slog;
-
-import java.util.ArrayList;
-import java.util.Objects;
-
-class TunerCallback extends ITunerCallback.Stub {
-    private static final String TAG = "BcRadio2Srv.cb";
-
-    final android.hardware.radio.ITunerCallback mClientCb;
-
-    interface RunnableThrowingRemoteException {
-        void run() throws RemoteException;
-    }
-
-    TunerCallback(@NonNull android.hardware.radio.ITunerCallback clientCallback) {
-        mClientCb = Objects.requireNonNull(clientCallback);
-    }
-
-    static void dispatch(RunnableThrowingRemoteException func) {
-        try {
-            func.run();
-        } catch (RemoteException ex) {
-            Slog.e(TAG, "callback call failed", ex);
-        }
-    }
-
-    @Override
-    public void onTuneFailed(int result, ProgramSelector selector) {
-        dispatch(() -> mClientCb.onTuneFailed(result, Convert.programSelectorFromHal(selector)));
-    }
-
-    @Override
-    public void onCurrentProgramInfoChanged(ProgramInfo info) {
-        dispatch(() -> mClientCb.onCurrentProgramInfoChanged(Convert.programInfoFromHal(info)));
-    }
-
-    @Override
-    public void onProgramListUpdated(ProgramListChunk chunk) {
-        dispatch(() -> mClientCb.onProgramListUpdated(Convert.programListChunkFromHal(chunk)));
-    }
-
-    @Override
-    public void onAntennaStateChange(boolean connected) {
-        dispatch(() -> mClientCb.onAntennaState(connected));
-    }
-
-    @Override
-    public void onParametersUpdated(ArrayList<VendorKeyValue> parameters) {
-        dispatch(() -> mClientCb.onParametersUpdated(Convert.vendorInfoFromHal(parameters)));
-    }
-}
diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
index 05ca144..008fea5 100644
--- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
@@ -43,7 +43,7 @@
 
     private final RadioModule mModule;
     private final ITunerSession mHwSession;
-    private final TunerCallback mCallback;
+    final android.hardware.radio.ITunerCallback mCallback;
     private boolean mIsClosed = false;
     private boolean mIsMuted = false;
 
@@ -51,7 +51,7 @@
     private RadioManager.BandConfig mDummyConfig = null;
 
     TunerSession(@NonNull RadioModule module, @NonNull ITunerSession hwSession,
-            @NonNull TunerCallback callback) {
+            @NonNull android.hardware.radio.ITunerCallback callback) {
         mModule = Objects.requireNonNull(module);
         mHwSession = Objects.requireNonNull(hwSession);
         mCallback = Objects.requireNonNull(callback);
@@ -73,9 +73,14 @@
         synchronized (mLock) {
             if (mIsClosed) return;
             if (error != null) {
-                TunerCallback.dispatch(() -> mCallback.mClientCb.onError(error));
+                try {
+                    mCallback.onError(error);
+                } catch (RemoteException ex) {
+                    Slog.w(TAG, "mCallback.onError() failed: ", ex);
+                }
             }
             mIsClosed = true;
+            mModule.onTunerSessionClosed(this);
         }
     }
 
@@ -96,7 +101,7 @@
             checkNotClosedLocked();
             mDummyConfig = Objects.requireNonNull(config);
             Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.x");
-            TunerCallback.dispatch(() -> mCallback.mClientCb.onConfigurationChanged(config));
+            mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config));
         }
     }
 
@@ -174,7 +179,7 @@
     @Override
     public boolean startBackgroundScan() {
         Slog.i(TAG, "Explicit background scan trigger is not supported with HAL 2.x");
-        TunerCallback.dispatch(() -> mCallback.mClientCb.onBackgroundScanComplete());
+        mModule.fanoutAidlCallback(cb -> cb.onBackgroundScanComplete());
         return true;
     }
 
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 0c55934..da1360d 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -37,7 +37,6 @@
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
 import android.net.INetd;
-import android.net.util.NetdService;
 import android.os.Build;
 import android.os.INetworkManagementService;
 import android.os.RemoteException;
@@ -77,7 +76,8 @@
     private final Context mContext;
     private final PackageManager mPackageManager;
     private final UserManager mUserManager;
-    private final INetworkManagementService mNetd;
+    private final INetworkManagementService mNMS;
+    private final INetd mNetd;
 
     // Values are User IDs.
     private final Set<Integer> mUsers = new HashSet<>();
@@ -100,6 +100,9 @@
                               app.requestedPermissionsFlags);
                     }
                 }
+            } else {
+                // The last package of this uid is removed from device. Clean the package up.
+                permission = INetd.PERMISSION_UNINSTALLED;
             }
             return permission;
         }
@@ -115,11 +118,12 @@
         }
     }
 
-    public PermissionMonitor(Context context, INetworkManagementService netd) {
+    public PermissionMonitor(Context context, INetworkManagementService nms, INetd netdService) {
         mContext = context;
         mPackageManager = context.getPackageManager();
         mUserManager = UserManager.get(context);
-        mNetd = netd;
+        mNMS = nms;
+        mNetd = netdService;
     }
 
     // Intended to be called only once at startup, after the system is ready. Installs a broadcast
@@ -285,11 +289,11 @@
         }
         try {
             if (add) {
-                mNetd.setPermission("NETWORK", toIntArray(network));
-                mNetd.setPermission("SYSTEM", toIntArray(system));
+                mNMS.setPermission("NETWORK", toIntArray(network));
+                mNMS.setPermission("SYSTEM", toIntArray(system));
             } else {
-                mNetd.clearPermission(toIntArray(network));
-                mNetd.clearPermission(toIntArray(system));
+                mNMS.clearPermission(toIntArray(network));
+                mNMS.clearPermission(toIntArray(system));
             }
         } catch (RemoteException e) {
             loge("Exception when updating permissions: " + e);
@@ -447,7 +451,8 @@
      *
      * @hide
      */
-    private void sendPackagePermissionsForUid(int uid, int permissions) {
+    @VisibleForTesting
+    void sendPackagePermissionsForUid(int uid, int permissions) {
         SparseIntArray netdPermissionsAppIds = new SparseIntArray();
         netdPermissionsAppIds.put(uid, permissions);
         sendPackagePermissionsToNetd(netdPermissionsAppIds);
@@ -462,15 +467,13 @@
      *
      * @hide
      */
-    private void sendPackagePermissionsToNetd(SparseIntArray netdPermissionsAppIds) {
-        INetd netdService = NetdService.getInstance();
-        if (netdService == null) {
-            Log.e(TAG, "Failed to get the netd service");
-            return;
-        }
+    @VisibleForTesting
+    void sendPackagePermissionsToNetd(SparseIntArray netdPermissionsAppIds) {
+
         ArrayList<Integer> allPermissionAppIds = new ArrayList<>();
         ArrayList<Integer> internetPermissionAppIds = new ArrayList<>();
         ArrayList<Integer> updateStatsPermissionAppIds = new ArrayList<>();
+        ArrayList<Integer> noPermissionAppIds = new ArrayList<>();
         ArrayList<Integer> uninstalledAppIds = new ArrayList<>();
         for (int i = 0; i < netdPermissionsAppIds.size(); i++) {
             int permissions = netdPermissionsAppIds.valueAt(i);
@@ -485,8 +488,10 @@
                     updateStatsPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
                     break;
                 case INetd.NO_PERMISSIONS:
-                    uninstalledAppIds.add(netdPermissionsAppIds.keyAt(i));
+                    noPermissionAppIds.add(netdPermissionsAppIds.keyAt(i));
                     break;
+                case INetd.PERMISSION_UNINSTALLED:
+                    uninstalledAppIds.add(netdPermissionsAppIds.keyAt(i));
                 default:
                     Log.e(TAG, "unknown permission type: " + permissions + "for uid: "
                             + netdPermissionsAppIds.keyAt(i));
@@ -495,20 +500,24 @@
         try {
             // TODO: add a lock inside netd to protect IPC trafficSetNetPermForUids()
             if (allPermissionAppIds.size() != 0) {
-                netdService.trafficSetNetPermForUids(
+                mNetd.trafficSetNetPermForUids(
                         INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS,
                         ArrayUtils.convertToIntArray(allPermissionAppIds));
             }
             if (internetPermissionAppIds.size() != 0) {
-                netdService.trafficSetNetPermForUids(INetd.PERMISSION_INTERNET,
+                mNetd.trafficSetNetPermForUids(INetd.PERMISSION_INTERNET,
                         ArrayUtils.convertToIntArray(internetPermissionAppIds));
             }
             if (updateStatsPermissionAppIds.size() != 0) {
-                netdService.trafficSetNetPermForUids(INetd.PERMISSION_UPDATE_DEVICE_STATS,
+                mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UPDATE_DEVICE_STATS,
                         ArrayUtils.convertToIntArray(updateStatsPermissionAppIds));
             }
+            if (noPermissionAppIds.size() != 0) {
+                mNetd.trafficSetNetPermForUids(INetd.NO_PERMISSIONS,
+                        ArrayUtils.convertToIntArray(noPermissionAppIds));
+            }
             if (uninstalledAppIds.size() != 0) {
-                netdService.trafficSetNetPermForUids(INetd.NO_PERMISSIONS,
+                mNetd.trafficSetNetPermForUids(INetd.PERMISSION_UNINSTALLED,
                         ArrayUtils.convertToIntArray(uninstalledAppIds));
             }
         } catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java
index 764a6eb..b0bbd72 100644
--- a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -52,7 +52,6 @@
 import android.provider.Settings;
 import android.telephony.CarrierConfigManager;
 import android.util.ArraySet;
-import android.util.Log;
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -231,7 +230,7 @@
 
     private void handleNotifyUpstream(boolean isCellular) {
         if (DBG) {
-            Log.d(TAG, "notifyUpstream: " + isCellular
+            mLog.i("notifyUpstream: " + isCellular
                     + ", mCellularUpstreamPermitted: " + mCellularUpstreamPermitted
                     + ", mNeedReRunProvisioningUi: " + mNeedReRunProvisioningUi);
         }
@@ -294,7 +293,7 @@
      * masterHandler to avoid race conditions.
      */
     public void reevaluateSimCardProvisioning() {
-        if (DBG) Log.d(TAG, "reevaluateSimCardProvisioning");
+        if (DBG) mLog.i("reevaluateSimCardProvisioning");
 
         if (!mHandler.getLooper().isCurrentThread()) {
             // Except for test, this log should not appear in normal flow.
@@ -351,7 +350,7 @@
      * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
      */
     protected void runSilentTetherProvisioning(int type) {
-        if (DBG) Log.d(TAG, "runSilentTetherProvisioning: " + type);
+        if (DBG) mLog.i("runSilentTetherProvisioning: " + type);
         // For silent provisioning, settings would stop tethering when entitlement fail.
         ResultReceiver receiver = buildProxyReceiver(type,
                 false/* notifyFail */, null);
@@ -382,7 +381,7 @@
 
     @VisibleForTesting
     protected void runUiTetherProvisioning(int type, ResultReceiver receiver) {
-        if (DBG) Log.d(TAG, "runUiTetherProvisioning: " + type);
+        if (DBG) mLog.i("runUiTetherProvisioning: " + type);
 
         Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING);
         intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
@@ -428,7 +427,7 @@
                 || mCellularPermitted.indexOfValue(TETHER_ERROR_NO_ERROR) > -1);
 
         if (DBG) {
-            Log.d(TAG, "Cellular permission change from " + oldPermitted
+            mLog.i("Cellular permission change from " + oldPermitted
                     + " to " + mCellularUpstreamPermitted);
         }
 
@@ -453,10 +452,8 @@
      * @param resultCode Provisioning result
      */
     protected void addDownstreamMapping(int type, int resultCode) {
-        if (DBG) {
-            Log.d(TAG, "addDownstreamMapping: " + type + ", result: " + resultCode
-                    + " ,TetherTypeRequested: " + mCurrentTethers.contains(type));
-        }
+        mLog.i("addDownstreamMapping: " + type + ", result: " + resultCode
+                + " ,TetherTypeRequested: " + mCurrentTethers.contains(type));
         if (!mCurrentTethers.contains(type)) return;
 
         mCellularPermitted.put(type, resultCode);
@@ -468,7 +465,7 @@
      * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
      */
     protected void removeDownstreamMapping(int type) {
-        if (DBG) Log.d(TAG, "removeDownstreamMapping: " + type);
+        mLog.i("removeDownstreamMapping: " + type);
         mCellularPermitted.delete(type);
         evaluateCellularPermission();
     }
@@ -617,7 +614,7 @@
      */
     private int updateEntitlementCacheValue(int type, int resultCode) {
         if (DBG) {
-            Log.d(TAG, "updateEntitlementCacheValue: " + type + ", result: " + resultCode);
+            mLog.i("updateEntitlementCacheValue: " + type + ", result: " + resultCode);
         }
         if (resultCode == TETHER_ERROR_NO_ERROR) {
             mEntitlementCacheValue.put(type, resultCode);
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
index 4e4b15f..ba4dcdb 100644
--- a/services/core/java/com/android/server/content/ContentService.java
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -1317,7 +1317,9 @@
         final int procState = ami.getUidProcessState(callingUid);
         final boolean isUidActive = ami.isUidActive(callingUid);
 
-        if (procState <= ActivityManager.PROCESS_STATE_TOP) {
+        // Providers bound by a TOP app will get PROCESS_STATE_BOUND_TOP, so include those as well
+        if (procState <= ActivityManager.PROCESS_STATE_TOP
+                || procState == ActivityManager.PROCESS_STATE_BOUND_TOP) {
             return ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET_WITH_TEMP;
         }
         if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND || isUidActive) {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 5abc73e..cec4d69 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -69,11 +69,13 @@
 import android.view.Display;
 import android.view.IInputFilter;
 import android.view.IInputFilterHost;
+import android.view.IInputMonitorHost;
 import android.view.IWindow;
 import android.view.InputApplicationHandle;
 import android.view.InputChannel;
 import android.view.InputDevice;
 import android.view.InputEvent;
+import android.view.InputMonitor;
 import android.view.InputWindowHandle;
 import android.view.KeyEvent;
 import android.view.PointerIcon;
@@ -202,7 +204,10 @@
             int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
     private static native void nativeRegisterInputChannel(long ptr, InputChannel inputChannel,
             int displayId);
+    private static native void nativeRegisterInputMonitor(long ptr, InputChannel inputChannel,
+            int displayId, boolean isGestureMonitor);
     private static native void nativeUnregisterInputChannel(long ptr, InputChannel inputChannel);
+    private static native void nativePilferPointers(long ptr, IBinder token);
     private static native void nativeSetInputFilterEnabled(long ptr, boolean enable);
     private static native int nativeInjectInputEvent(long ptr, InputEvent event,
             int injectorPid, int injectorUid, int syncMode, int timeoutMillis,
@@ -489,12 +494,43 @@
         }
 
         InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
-        nativeRegisterInputChannel(mPtr, inputChannels[0], displayId);
+        // Give the output channel a token just for identity purposes.
+        inputChannels[0].setToken(new Binder());
+        nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId, false /*isGestureMonitor*/);
         inputChannels[0].dispose(); // don't need to retain the Java object reference
         return inputChannels[1];
     }
 
     /**
+     * Creates an input monitor that will receive pointer events for the purposes of system-wide
+     * gesture interpretation.
+     *
+     * @param inputChannelName The input channel name.
+     * @param displayId Target display id.
+     * @return The input channel.
+     */
+    @Override // Binder call
+    public InputMonitor monitorGestureInput(String inputChannelName, int displayId) {
+        if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT,
+                "monitorInputRegion()")) {
+            throw new SecurityException("Requires MONITOR_INPUT permission");
+        }
+
+        Objects.requireNonNull(inputChannelName, "inputChannelName must not be null.");
+
+        if (displayId < Display.DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException("displayId must >= 0.");
+        }
+
+
+        InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
+        InputMonitorHost host = new InputMonitorHost(inputChannels[0]);
+        inputChannels[0].setToken(host.asBinder());
+        nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId, true /*isGestureMonitor*/);
+        return new InputMonitor(inputChannelName, inputChannels[1], host);
+    }
+
+    /**
      * Registers an input channel so that it can be used as an input event target.
      * @param inputChannel The input channel to register.
      * @param inputWindowHandle The handle of the input window associated with the
@@ -1810,6 +1846,7 @@
 
     // Native callback.
     private void onPointerDownOutsideFocus(IBinder touchedToken) {
+        mWindowManagerCallbacks.onPointerDownOutsideFocus(touchedToken);
     }
 
     // Native callback.
@@ -2024,6 +2061,14 @@
         public int getPointerLayer();
 
         public int getPointerDisplayId();
+
+        /**
+         * Notifies window manager that a {@link android.view.MotionEvent#ACTION_DOWN} pointer event
+         * occurred on a window that did not have focus.
+         *
+         * @param touchedToken The token for the window that received the input event.
+         */
+        void onPointerDownOutsideFocus(IBinder touchedToken);
     }
 
     /**
@@ -2096,6 +2141,28 @@
         }
     }
 
+    /**
+     * Interface for the system to handle request from InputMonitors.
+     */
+    private final class InputMonitorHost extends IInputMonitorHost.Stub {
+        private final InputChannel mInputChannel;
+
+        InputMonitorHost(InputChannel channel) {
+            mInputChannel = channel;
+        }
+
+        @Override
+        public void pilferPointers() {
+            nativePilferPointers(mPtr, asBinder());
+        }
+
+        @Override
+        public void dispose() {
+            nativeUnregisterInputChannel(mPtr, mInputChannel);
+            mInputChannel.dispose();
+        }
+    }
+
     private static final class KeyboardLayoutDescriptor {
         public String packageName;
         public String receiverName;
diff --git a/services/core/java/com/android/server/location/GnssCapabilitiesProvider.java b/services/core/java/com/android/server/location/GnssCapabilitiesProvider.java
new file mode 100644
index 0000000..98085b8
--- /dev/null
+++ b/services/core/java/com/android/server/location/GnssCapabilitiesProvider.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.location.GnssCapabilities;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Provides GNSS capabilities supported by the GNSS HAL implementation.
+ */
+public class GnssCapabilitiesProvider {
+    private static final String TAG = "GnssCapabilitiesProvider";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // Bit masks for capabilities in {@link android.location.GnssCapabilities}.
+    private static final long GNSS_CAPABILITY_LOW_POWER_MODE =
+            1L << GnssCapabilities.LOW_POWER_MODE;
+    private static final long GNSS_CAPABILITY_SATELLITE_BLACKLIST =
+            1L << GnssCapabilities.SATELLITE_BLACKLIST;
+    private static final long GNSS_CAPABILITY_GEOFENCING = 1L << GnssCapabilities.GEOFENCING;
+    private static final long GNSS_CAPABILITY_MEASUREMENTS = 1L << GnssCapabilities.MEASUREMENTS;
+    private static final long GNSS_CAPABILITY_NAV_MESSAGES = 1L << GnssCapabilities.NAV_MESSAGES;
+    private static final long GNSS_CAPABILITY_MEASUREMENT_CORRECTIONS =
+            1L << GnssCapabilities.MEASUREMENT_CORRECTIONS;
+    private static final long GNSS_CAPABILITY_MEASUREMENT_CORRECTIONS_LOS_SATS =
+            1L << GnssCapabilities.MEASUREMENT_CORRECTIONS_LOS_SATS;
+    private static final long GNSS_CAPABILITY_MEASUREMENT_CORRECTIONS_EXCESS_PATH_LENGTH =
+            1L << GnssCapabilities.MEASUREMENT_CORRECTIONS_EXCESS_PATH_LENGTH;
+    private static final long GNSS_CAPABILITY_MEASUREMENT_CORRECTIONS_REFLECTING_PLANE =
+            1L << GnssCapabilities.MEASUREMENT_CORRECTIONS_REFLECTING_PLANE;
+
+    private static final long GNSS_CAPABILITIES_TOP_HAL =
+            GNSS_CAPABILITY_LOW_POWER_MODE | GNSS_CAPABILITY_SATELLITE_BLACKLIST
+                    | GNSS_CAPABILITY_GEOFENCING | GNSS_CAPABILITY_MEASUREMENTS
+                    | GNSS_CAPABILITY_NAV_MESSAGES;
+
+    private static final long GNSS_CAPABILITIES_SUB_HAL_MEASUREMENT_CORRECTIONS =
+            GNSS_CAPABILITY_MEASUREMENT_CORRECTIONS
+                    | GNSS_CAPABILITY_MEASUREMENT_CORRECTIONS_LOS_SATS
+                    | GNSS_CAPABILITY_MEASUREMENT_CORRECTIONS_EXCESS_PATH_LENGTH
+                    | GNSS_CAPABILITY_MEASUREMENT_CORRECTIONS_REFLECTING_PLANE;
+
+    // Capabilities in {@link android.location.GnssCapabilities} supported by GNSS chipset.
+    @GuardedBy("this")
+    private long mGnssCapabilities;
+
+    /**
+     * Returns the capabilities supported by the GNSS chipset.
+     *
+     * <p>The capabilities are described in {@link android.location.GnssCapabilities} and
+     * their integer values correspond to the bit positions in the returned {@code long} value.
+     */
+    public long getGnssCapabilities() {
+        synchronized (this) {
+            return mGnssCapabilities;
+        }
+    }
+
+    /**
+     * Updates the general capabilities exposed through {@link android.location.GnssCapabilities}.
+     */
+    void setTopHalCapabilities(int topHalCapabilities,
+            boolean hasGeofencingCapability, boolean hasMeasurementsCapability,
+            boolean hasNavMessagesCapability) {
+        long gnssCapabilities = 0;
+        if (hasCapability(topHalCapabilities,
+                GnssLocationProvider.GPS_CAPABILITY_LOW_POWER_MODE)) {
+            gnssCapabilities |= GNSS_CAPABILITY_LOW_POWER_MODE;
+        }
+        if (hasCapability(topHalCapabilities,
+                GnssLocationProvider.GPS_CAPABILITY_SATELLITE_BLACKLIST)) {
+            gnssCapabilities |= GNSS_CAPABILITY_SATELLITE_BLACKLIST;
+        }
+        if (hasGeofencingCapability) {
+            gnssCapabilities |= GNSS_CAPABILITY_GEOFENCING;
+        }
+        if (hasMeasurementsCapability) {
+            gnssCapabilities |= GNSS_CAPABILITY_MEASUREMENTS;
+        }
+        if (hasNavMessagesCapability) {
+            gnssCapabilities |= GNSS_CAPABILITY_NAV_MESSAGES;
+        }
+
+        synchronized (this) {
+            mGnssCapabilities &= ~GNSS_CAPABILITIES_TOP_HAL;
+            mGnssCapabilities |= gnssCapabilities;
+            if (DEBUG) {
+                Log.d(TAG, "setTopHalCapabilities, mGnssCapabilities=0x" + Long.toHexString(
+                        mGnssCapabilities) + ", " + GnssCapabilities.of(mGnssCapabilities));
+            }
+        }
+    }
+
+    /**
+     * Updates the measurement corrections related capabilities exposed through
+     * {@link android.location.GnssCapabilities}.
+     */
+    void setSubHalMeasurementCorrectionsCapabilities(int measurementCorrectionsCapabilities) {
+        long gnssCapabilities = GNSS_CAPABILITY_MEASUREMENT_CORRECTIONS;
+        if (hasCapability(measurementCorrectionsCapabilities,
+                GnssMeasurementCorrectionsProvider.CAPABILITY_LOS_SATS)) {
+            gnssCapabilities |= GNSS_CAPABILITY_MEASUREMENT_CORRECTIONS_LOS_SATS;
+        }
+        if (hasCapability(measurementCorrectionsCapabilities,
+                GnssMeasurementCorrectionsProvider.CAPABILITY_EXCESS_PATH_LENGTH)) {
+            gnssCapabilities |= GNSS_CAPABILITY_MEASUREMENT_CORRECTIONS_EXCESS_PATH_LENGTH;
+        }
+        if (hasCapability(measurementCorrectionsCapabilities,
+                GnssMeasurementCorrectionsProvider.CAPABILITY_REFLECTING_PLANE)) {
+            gnssCapabilities |= GNSS_CAPABILITY_MEASUREMENT_CORRECTIONS_REFLECTING_PLANE;
+        }
+
+        synchronized (this) {
+            mGnssCapabilities &= ~GNSS_CAPABILITIES_SUB_HAL_MEASUREMENT_CORRECTIONS;
+            mGnssCapabilities |= gnssCapabilities;
+            if (DEBUG) {
+                Log.d(TAG, "setSubHalMeasurementCorrectionsCapabilities, mGnssCapabilities=0x"
+                        + Long.toHexString(mGnssCapabilities) + ", " + GnssCapabilities.of(
+                        mGnssCapabilities));
+            }
+        }
+    }
+
+    private static  boolean hasCapability(int halCapabilities, int capability) {
+        return (halCapabilities & capability) != 0;
+    }
+}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 6066fc3..be34adb1 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -173,8 +173,8 @@
     public static final int GPS_CAPABILITY_MEASUREMENTS = 0x0000040;
     public static final int GPS_CAPABILITY_NAV_MESSAGES = 0x0000080;
 
-    private static final int GPS_CAPABILITY_LOW_POWER_MODE = 0x0000100;
-    private static final int GPS_CAPABILITY_SATELLITE_BLACKLIST = 0x0000200;
+    static final int GPS_CAPABILITY_LOW_POWER_MODE = 0x0000100;
+    static final int GPS_CAPABILITY_SATELLITE_BLACKLIST = 0x0000200;
 
     // The AGPS SUPL mode
     private static final int AGPS_SUPL_MODE_MSA = 0x02;
@@ -338,8 +338,8 @@
     // true if we started navigation
     private boolean mStarted;
 
-    // capabilities of the GPS engine
-    private volatile int mEngineCapabilities;
+    // capabilities reported through the top level IGnssCallback.hal
+    private volatile int mTopHalCapabilities;
 
     // true if XTRA is supported
     private boolean mSupportsXtra;
@@ -385,6 +385,8 @@
     private final NtpTimeHelper mNtpTimeHelper;
     private final GnssBatchingProvider mGnssBatchingProvider;
     private final GnssGeofenceProvider mGnssGeofenceProvider;
+    private final GnssCapabilitiesProvider mGnssCapabilitiesProvider;
+
     // Available only on GNSS HAL 2.0 implementations and later.
     private GnssVisibilityControl mGnssVisibilityControl;
 
@@ -593,10 +595,8 @@
         mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0);
         mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0);
 
-        mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(
-                context,
-                GnssLocationProvider.this::onNetworkAvailable,
-                looper);
+        mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context,
+                GnssLocationProvider.this::onNetworkAvailable, looper);
 
         // App ops service to keep track of who is accessing the GPS
         mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -614,6 +614,7 @@
         // while IO initialization and registration is delegated to our internal handler
         // this approach is just fine because events are posted to our handler anyway
         mGnssConfiguration = new GnssConfiguration(mContext);
+        mGnssCapabilitiesProvider = new GnssCapabilitiesProvider();
         // Create a GPS net-initiated handler (also needed by handleInitialize)
         mNIHandler = new GpsNetInitiatedHandler(context,
                 mNetInitiatedListener,
@@ -1280,12 +1281,8 @@
         mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, now + mFixInterval, mWakeupIntent);
     }
 
-    public int getGnssCapabilities() {
-        return mEngineCapabilities;
-    }
-
     private boolean hasCapability(int capability) {
-        return ((mEngineCapabilities & capability) != 0);
+        return (mTopHalCapabilities & capability) != 0;
     }
 
     @NativeEntryPoint
@@ -1493,27 +1490,52 @@
     }
 
     @NativeEntryPoint
-    private void setEngineCapabilities(final int capabilities, boolean hasSubHalCapabilityFlags) {
-        // send to handler thread for fast native return, and in-order handling
+    private void setTopHalCapabilities(int topHalCapabilities, boolean hasSubHalCapabilityFlags) {
+        // The IGnssCallback.hal@2.0 removed sub-HAL capability flags from the Capabilities enum
+        // and instead uses the sub-HAL non-null handle returned from IGnss.hal@2.0 to indicate
+        // support. Therefore, the 'hasSubHalCapabilityFlags' parameter is needed to tell if the
+        // 'capabilities' parameter includes the sub-HAL capability flags or not. Old HALs
+        // which explicitly set the sub-HAL capability bits must continue to work.
         mHandler.post(() -> {
-            mEngineCapabilities = capabilities;
+            mTopHalCapabilities = topHalCapabilities;
 
             if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) {
                 mNtpTimeHelper.enablePeriodicTimeInjection();
                 requestUtcTime();
             }
 
-            mGnssMeasurementsProvider.onCapabilitiesUpdated(capabilities, hasSubHalCapabilityFlags);
-            mGnssNavigationMessageProvider.onCapabilitiesUpdated(capabilities,
-                    hasSubHalCapabilityFlags);
+            boolean hasGeofencingCapability;
+            boolean hasMeasurementsCapability;
+            boolean hasNavMessagesCapability;
+            if (hasSubHalCapabilityFlags) {
+                hasGeofencingCapability = hasCapability(GPS_CAPABILITY_GEOFENCING);
+                hasMeasurementsCapability = hasCapability(GPS_CAPABILITY_MEASUREMENTS);
+                hasNavMessagesCapability = hasCapability(GPS_CAPABILITY_NAV_MESSAGES);
+            } else {
+                hasGeofencingCapability = mGnssGeofenceProvider.isHardwareGeofenceSupported();
+                hasMeasurementsCapability = mGnssMeasurementsProvider.isAvailableInPlatform();
+                hasNavMessagesCapability = mGnssNavigationMessageProvider.isAvailableInPlatform();
+            }
+
+            mGnssMeasurementsProvider.onCapabilitiesUpdated(hasMeasurementsCapability);
+            mGnssNavigationMessageProvider.onCapabilitiesUpdated(hasNavMessagesCapability);
             restartRequests();
+
+            mGnssCapabilitiesProvider.setTopHalCapabilities(topHalCapabilities,
+                    hasGeofencingCapability, hasMeasurementsCapability, hasNavMessagesCapability);
         });
     }
 
     @NativeEntryPoint
-    private void setMeasurementCorrectionsCapabilities(final int capabilities) {
-        mHandler.post(() -> mGnssMeasurementCorrectionsProvider.onCapabilitiesUpdated(
-                capabilities));
+    private void setSubHalMeasurementCorrectionsCapabilities(int subHalCapabilities) {
+        mHandler.post(() -> {
+            if (!mGnssMeasurementCorrectionsProvider.onCapabilitiesUpdated(subHalCapabilities)) {
+                return;
+            }
+
+            mGnssCapabilitiesProvider.setSubHalMeasurementCorrectionsCapabilities(
+                    subHalCapabilities);
+        });
     }
 
     private void restartRequests() {
@@ -1614,6 +1636,13 @@
         return () -> mGnssMetrics.dumpGnssMetricsAsProtoString();
     }
 
+    /**
+     * @hide
+     */
+    public GnssCapabilitiesProvider getGnssCapabilitiesProvider() {
+        return mGnssCapabilitiesProvider;
+    }
+
     @NativeEntryPoint
     private void reportLocationBatch(Location[] locationArray) {
         List<Location> locations = new ArrayList<>(Arrays.asList(locationArray));
@@ -2143,7 +2172,7 @@
         s.append("  mGnssNavigationMessageProvider.isRegistered()=")
                 .append(mGnssNavigationMessageProvider.isRegistered()).append('\n');
         s.append("  mDisableGpsForPowerManager=").append(mDisableGpsForPowerManager).append('\n');
-        s.append("  mEngineCapabilities=0x").append(Integer.toHexString(mEngineCapabilities));
+        s.append("  mTopHalCapabilities=0x").append(Integer.toHexString(mTopHalCapabilities));
         s.append(" ( ");
         if (hasCapability(GPS_CAPABILITY_SCHEDULING)) s.append("SCHEDULING ");
         if (hasCapability(GPS_CAPABILITY_MSB)) s.append("MSB ");
diff --git a/services/core/java/com/android/server/location/GnssMeasurementCorrectionsProvider.java b/services/core/java/com/android/server/location/GnssMeasurementCorrectionsProvider.java
index 2162787..82528ca 100644
--- a/services/core/java/com/android/server/location/GnssMeasurementCorrectionsProvider.java
+++ b/services/core/java/com/android/server/location/GnssMeasurementCorrectionsProvider.java
@@ -34,9 +34,9 @@
     private static final String TAG = "GnssMeasurementCorrectionsProvider";
 
     // These must match with the Capabilities enum in IMeasurementCorrectionsCallback.hal.
-    private static final int CAPABILITY_LOS_SATS = 0x0000001;
-    private static final int CAPABILITY_EXCESS_PATH_LENGTH = 0x0000002;
-    private static final int CAPABILITY_REFLECTING_PLANE = 0x0000004;
+    static final int CAPABILITY_LOS_SATS = 0x0000001;
+    static final int CAPABILITY_EXCESS_PATH_LENGTH = 0x0000002;
+    static final int CAPABILITY_REFLECTING_PLANE = 0x0000004;
 
     private static final int INVALID_CAPABILITIES = 1 << 31;
 
@@ -83,21 +83,23 @@
     }
 
     /** Handle measurement corrections capabilities update from the GNSS HAL implementation. */
-    void onCapabilitiesUpdated(int capabilities) {
+    boolean onCapabilitiesUpdated(int capabilities) {
         if (hasCapability(capabilities, CAPABILITY_LOS_SATS) || hasCapability(capabilities,
                 CAPABILITY_EXCESS_PATH_LENGTH)) {
             mCapabilities = capabilities;
+            return true;
         } else {
             Log.e(TAG, "Failed to set capabilities. Received capabilities 0x"
                     + Integer.toHexString(capabilities) + " does not contain the mandatory "
                     + "LOS_SATS or the EXCESS_PATH_LENGTH capability.");
+            return false;
         }
     }
 
     /**
      * Returns the measurement corrections specific capabilities of the GNSS HAL implementation.
      */
-    public int getCapabilities() {
+    int getCapabilities() {
         return mCapabilities;
     }
 
@@ -122,14 +124,14 @@
         return s.toString();
     }
 
-    private boolean isCapabilitiesReceived() {
-        return mCapabilities != INVALID_CAPABILITIES;
-    }
-
     private static  boolean hasCapability(int halCapabilities, int capability) {
         return (halCapabilities & capability) != 0;
     }
 
+    private boolean isCapabilitiesReceived() {
+        return mCapabilities != INVALID_CAPABILITIES;
+    }
+
     @VisibleForTesting
     static class GnssMeasurementCorrectionsProviderNative {
         public boolean isMeasurementCorrectionsSupported() {
diff --git a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java
index 844735a..ec05c31 100644
--- a/services/core/java/com/android/server/location/GnssMeasurementsProvider.java
+++ b/services/core/java/com/android/server/location/GnssMeasurementsProvider.java
@@ -105,15 +105,7 @@
     }
 
     /** Handle GNSS capabilities update from the GNSS HAL implementation. */
-    public void onCapabilitiesUpdated(int capabilities, boolean hasSubHalCapabilityFlags) {
-        // The IGnssCallback.hal@2.0 removed sub-HAL capability flags from the Capabilities enum
-        // and instead uses the sub-HAL non-null handle returned from IGnss.hal@2.0 to indicate
-        // support. Therefore, the 'hasSubHalCapabilityFlags' parameter is needed to tell if the
-        // 'capabilities' parameter includes the sub-HAL capability flags or not. Old HALs
-        // which explicitly set the sub-HAL capability bits must continue to work.
-        final boolean isGnssMeasurementsSupported = hasSubHalCapabilityFlags
-                ? (capabilities & GnssLocationProvider.GPS_CAPABILITY_MEASUREMENTS) != 0
-                : mNative.isMeasurementSupported();
+    public void onCapabilitiesUpdated(boolean isGnssMeasurementsSupported) {
         setSupported(isGnssMeasurementsSupported);
         updateResult();
     }
diff --git a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
index 7e8b599..4c45ef6 100644
--- a/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
+++ b/services/core/java/com/android/server/location/GnssNavigationMessageProvider.java
@@ -92,15 +92,7 @@
     }
 
     /** Handle GNSS capabilities update from the GNSS HAL implementation */
-    public void onCapabilitiesUpdated(int capabilities, boolean hasSubHalCapabilityFlags) {
-        // The IGnssCallback.hal@2.0 removed sub-HAL capability flags from the Capabilities enum
-        // and instead uses the sub-HAL non-null handle returned from IGnss.hal@2.0 to indicate
-        // support. Therefore, the 'hasSubHalCapabilityFlags' parameter is needed to tell if the
-        // 'capabilities' parameter includes the sub-HAL capability flags or not. Old HALs
-        // which explicitly set the sub-HAL capability bits must continue to work.
-        final boolean isGnssNavigationMessageSupported = hasSubHalCapabilityFlags
-                ? (capabilities & GnssLocationProvider.GPS_CAPABILITY_NAV_MESSAGES) != 0
-                : mNative.isNavigationMessageSupported();
+    public void onCapabilitiesUpdated(boolean isGnssNavigationMessageSupported) {
         setSupported(isGnssNavigationMessageSupported);
         updateResult();
     }
diff --git a/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java
index c06b03b..ab75b21 100644
--- a/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/GnssNetworkConnectivityHandler.java
@@ -27,6 +27,7 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.provider.Telephony.Carriers;
+import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
@@ -591,13 +592,25 @@
         }
         TelephonyManager phone = (TelephonyManager)
                 mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        ServiceState serviceState = phone.getServiceState();
+        String projection = null;
+        String selection = null;
+
         // Carrier configuration may override framework roaming state, we need to use the actual
         // modem roaming state instead of the framework roaming state.
-        boolean isDataRoamingFromRegistration = phone.getServiceState()
-                .getDataRoamingFromRegistration();
-        String projection = isDataRoamingFromRegistration ? Carriers.ROAMING_PROTOCOL :
-                Carriers.PROTOCOL;
-        String selection = String.format("current = 1 and apn = '%s' and carrier_enabled = 1", apn);
+        if (serviceState != null && serviceState.getDataRoamingFromRegistration()) {
+            projection = Carriers.ROAMING_PROTOCOL;
+        } else {
+            projection = Carriers.PROTOCOL;
+        }
+        // No SIM case for emergency
+        if (TelephonyManager.NETWORK_TYPE_UNKNOWN == phone.getNetworkType()
+                && AGPS_TYPE_EIMS == mAGpsType) {
+            selection = String.format(
+                "type like '%%emergency%%' and apn = '%s' and carrier_enabled = 1", apn);
+        } else {
+            selection = String.format("current = 1 and apn = '%s' and carrier_enabled = 1", apn);
+        }
         try (Cursor cursor = mContext.getContentResolver().query(
                 Carriers.CONTENT_URI,
                 new String[]{projection},
@@ -613,7 +626,7 @@
             Log.e(TAG, "Error encountered on APN query for: " + apn, e);
         }
 
-        return APN_INVALID;
+        return APN_IPV4V6;
     }
 
     private int translateToApnIpType(String ipProtocol, String apn) {
@@ -630,7 +643,7 @@
         // we hit the default case so the ipProtocol is not recognized
         String message = String.format("Unknown IP Protocol: %s, for APN: %s", ipProtocol, apn);
         Log.e(TAG, message);
-        return APN_INVALID;
+        return APN_IPV4V6;
     }
 
     // AGPS support
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 3e134b2..da836c2 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -94,6 +94,7 @@
 import android.service.gatekeeper.IGateKeeperService;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
@@ -141,6 +142,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -324,7 +326,7 @@
             Arrays.fill(newPasswordChars, '\u0000');
             final int quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
             setLockCredentialInternal(newPassword, CREDENTIAL_TYPE_PASSWORD, managedUserPassword,
-                    quality, managedUserId, false);
+                    quality, managedUserId, false, /* isLockTiedToParent= */ true);
             // We store a private credential for the managed user that's unlocked by the primary
             // account holder's credential. As such, the user will never be prompted to enter this
             // password directly, so we always store a password.
@@ -1303,13 +1305,13 @@
                         setLockCredentialInternal(null, CREDENTIAL_TYPE_NONE,
                                 profilePasswordMap.get(managedUserId),
                                 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, managedUserId,
-                                false);
+                                false, /* isLockTiedToParent= */ true);
                     } else {
                         Slog.wtf(TAG, "clear tied profile challenges, but no password supplied.");
                         // Supplying null here would lead to untrusted credential change
                         setLockCredentialInternal(null, CREDENTIAL_TYPE_NONE, null,
                                 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, managedUserId,
-                                true);
+                                true, /* isLockTiedToParent= */ true);
                     }
                     mStorage.removeChildProfileLock(managedUserId);
                     removeKeystoreProfileKey(managedUserId);
@@ -1328,6 +1330,67 @@
                 && mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
     }
 
+    /**
+     * Send credentials for user {@code userId} to {@link RecoverableKeyStoreManager} during an
+     * unlock operation.
+     */
+    private void sendCredentialsOnUnlockIfRequired(
+            int credentialType, @NonNull byte[] credential, int userId) {
+        // Don't send credentials during the factory reset protection flow.
+        if (userId == USER_FRP) {
+            return;
+        }
+
+        // A profile with a unified lock screen stores a randomly generated credential, so skip it.
+        // Its parent will send credentials for the profile, as it stores the unified lock
+        // credential.
+        if (isManagedProfileWithUnifiedLock(userId)) {
+            return;
+        }
+
+        // Send credentials for the user and any child profiles that share its lock screen.
+        for (int profileId : getProfilesWithSameLockScreen(userId)) {
+            mRecoverableKeyStoreManager.lockScreenSecretAvailable(
+                    credentialType, credential, profileId);
+        }
+    }
+
+    /**
+     * Send credentials for user {@code userId} to {@link RecoverableKeyStoreManager} when its
+     * credentials are set/changed.
+     */
+    private void sendCredentialsOnChangeIfRequired(
+            int credentialType, byte[] credential, int userId, boolean isLockTiedToParent) {
+        // A profile whose lock screen is being tied to its parent's will either have a randomly
+        // generated credential (creation) or null (removal). We rely on the parent to send its
+        // credentials for the profile in both cases as it stores the unified lock credential.
+        if (isLockTiedToParent) {
+            return;
+        }
+
+        // Send credentials for the user and any child profiles that share its lock screen.
+        for (int profileId : getProfilesWithSameLockScreen(userId)) {
+            mRecoverableKeyStoreManager.lockScreenSecretChanged(
+                    credentialType, credential, profileId);
+        }
+    }
+
+    /**
+     * Returns all profiles of {@code userId}, including itself, that have the same lock screen
+     * challenge.
+     */
+    private Set<Integer> getProfilesWithSameLockScreen(int userId) {
+        Set<Integer> profiles = new ArraySet<>();
+        for (UserInfo profile : mUserManager.getProfiles(userId)) {
+            if (profile.id == userId
+                    || (profile.profileGroupId == userId
+                            && isManagedProfileWithUnifiedLock(profile.id))) {
+                profiles.add(profile.id);
+            }
+        }
+        return profiles;
+    }
+
     // This method should be called by LockPatternUtil only, all internal methods in this class
     // should call setLockCredentialInternal.
     @Override
@@ -1342,16 +1405,24 @@
         checkWritePermission(userId);
         synchronized (mSeparateChallengeLock) {
             setLockCredentialInternal(credential, type, savedCredential, requestedQuality, userId,
-                    allowUntrustedChange);
+                    allowUntrustedChange, /* isLockTiedToParent= */ false);
             setSeparateProfileChallengeEnabledLocked(userId, true, null);
             notifyPasswordChanged(userId);
         }
+        if (mUserManager.getUserInfo(userId).isManagedProfile()) {
+            // Make sure the profile doesn't get locked straight after setting work challenge.
+            setDeviceUnlockedForUser(userId);
+        }
         notifySeparateProfileChallengeChanged(userId);
     }
 
+    /**
+     * @param isLockTiedToParent is {@code true} if {@code userId} is a profile and its new
+     *     credentials are being tied to its parent's credentials.
+     */
     private void setLockCredentialInternal(byte[] credential, @CredentialType int credentialType,
-            byte[] savedCredential, int requestedQuality, int userId,
-            boolean allowUntrustedChange) throws RemoteException {
+            byte[] savedCredential, int requestedQuality, int userId, boolean allowUntrustedChange,
+            boolean isLockTiedToParent) throws RemoteException {
         // Normalize savedCredential and credential such that empty string is always represented
         // as null.
         if (savedCredential == null || savedCredential.length == 0) {
@@ -1363,7 +1434,7 @@
         synchronized (mSpManager) {
             if (isSyntheticPasswordBasedCredentialLocked(userId)) {
                 spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
-                        requestedQuality, userId, allowUntrustedChange);
+                        requestedQuality, userId, allowUntrustedChange, isLockTiedToParent);
                 return;
             }
         }
@@ -1379,7 +1450,8 @@
             fixateNewestUserKeyAuth(userId);
             synchronizeUnifiedWorkChallengeForProfiles(userId, null);
             notifyActivePasswordMetricsAvailable(CREDENTIAL_TYPE_NONE, null, userId);
-            mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential, userId);
+            sendCredentialsOnChangeIfRequired(
+                    credentialType, credential, userId, isLockTiedToParent);
             return;
         }
         if (credential == null) {
@@ -1414,7 +1486,7 @@
                 initializeSyntheticPasswordLocked(currentHandle.hash, savedCredential,
                         currentHandle.type, requestedQuality, userId);
                 spBasedSetLockCredentialInternalLocked(credential, credentialType, savedCredential,
-                        requestedQuality, userId, allowUntrustedChange);
+                        requestedQuality, userId, allowUntrustedChange, isLockTiedToParent);
                 return;
             }
         }
@@ -1432,8 +1504,8 @@
             // Refresh the auth token
             doVerifyCredential(credential, credentialType, true, 0, userId, null /* progressCallback */);
             synchronizeUnifiedWorkChallengeForProfiles(userId, null);
-            mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential,
-                userId);
+            sendCredentialsOnChangeIfRequired(
+                    credentialType, credential, userId, isLockTiedToParent);
         } else {
             throw new RemoteException("Failed to enroll " +
                     (credentialType == CREDENTIAL_TYPE_PASSWORD ? "password" : "pattern"));
@@ -1674,8 +1746,7 @@
         // The user employs synthetic password based credential.
         if (response != null) {
             if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-                mRecoverableKeyStoreManager.lockScreenSecretAvailable(credentialType, credential,
-                        userId);
+                sendCredentialsOnUnlockIfRequired(credentialType, credential, userId);
             }
             return response;
         }
@@ -1709,7 +1780,8 @@
             mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
             if (shouldReEnrollBaseZero) {
                 setLockCredentialInternal(credential, storedHash.type, credentialToVerify,
-                        DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId, false);
+                        DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, userId, false,
+                        /* isLockTiedToParent= */ false);
             }
         }
 
@@ -1800,12 +1872,12 @@
                         storedHash.type == CREDENTIAL_TYPE_PATTERN
                                 ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
                                 : DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
-                                /* TODO(roosa): keep the same password quality */, userId, false);
+                                /* TODO(roosa): keep the same password quality */,
+                        userId, false, /* isLockTiedToParent= */ false);
                 if (!hasChallenge) {
                     notifyActivePasswordMetricsAvailable(storedHash.type, credential, userId);
                     // Use credentials to create recoverable keystore snapshot.
-                    mRecoverableKeyStoreManager.lockScreenSecretAvailable(
-                            storedHash.type, credential, userId);
+                    sendCredentialsOnUnlockIfRequired(storedHash.type, credential, userId);
                     return VerifyCredentialResponse.OK;
                 }
                 // Fall through to get the auth token. Technically this should never happen,
@@ -1835,9 +1907,7 @@
             unlockUser(userId, response.getPayload(), secretFromCredential(credential));
 
             if (isManagedProfileWithSeparatedLock(userId)) {
-                TrustManager trustManager =
-                        (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
-                trustManager.setDeviceLockedForUser(userId, false);
+                setDeviceUnlockedForUser(userId);
             }
             int reEnrollQuality = storedHash.type == CREDENTIAL_TYPE_PATTERN
                     ? DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
@@ -1845,7 +1915,7 @@
                     /* TODO(roosa): keep the same password quality */;
             if (shouldReEnroll) {
                 setLockCredentialInternal(credential, storedHash.type, credential,
-                        reEnrollQuality, userId, false);
+                        reEnrollQuality, userId, false, /* isLockTiedToParent= */ false);
             } else {
                 // Now that we've cleared of all required GK migration, let's do the final
                 // migration to synthetic password.
@@ -1859,8 +1929,7 @@
                 }
             }
             // Use credentials to create recoverable keystore snapshot.
-            mRecoverableKeyStoreManager.lockScreenSecretAvailable(storedHash.type, credential,
-                userId);
+            sendCredentialsOnUnlockIfRequired(storedHash.type, credential, userId);
 
         } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
             if (response.getTimeout() > 0) {
@@ -2465,9 +2534,7 @@
             activateEscrowTokens(authResult.authToken, userId);
 
             if (isManagedProfileWithSeparatedLock(userId)) {
-                TrustManager trustManager =
-                        (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
-                trustManager.setDeviceLockedForUser(userId, false);
+                setDeviceUnlockedForUser(userId);
             }
             mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
 
@@ -2481,6 +2548,11 @@
         return response;
     }
 
+    private void setDeviceUnlockedForUser(int userId) {
+        final TrustManager trustManager = mContext.getSystemService(TrustManager.class);
+        trustManager.setDeviceLockedForUser(userId, false);
+    }
+
     /**
      * Change the user's lockscreen password by creating a new SP blob and update the handle, based
      * on an existing authentication token. Even though a new SP blob is created, the underlying
@@ -2549,7 +2621,7 @@
     @GuardedBy("mSpManager")
     private void spBasedSetLockCredentialInternalLocked(byte[] credential, int credentialType,
             byte[] savedCredential, int requestedQuality, int userId,
-            boolean allowUntrustedChange) throws RemoteException {
+            boolean allowUntrustedChange, boolean isLockTiedToParent) throws RemoteException {
         if (DEBUG) Slog.d(TAG, "spBasedSetLockCredentialInternalLocked: user=" + userId);
         if (isManagedProfileWithUnifiedLock(userId)) {
             // get credential from keystore when managed profile has unified lock
@@ -2615,7 +2687,7 @@
             // requestedQuality, userId) instead if we still allow untrusted reset that changes
             // synthetic password. That would invalidate existing escrow tokens though.
         }
-        mRecoverableKeyStoreManager.lockScreenSecretChanged(credentialType, credential, userId);
+        sendCredentialsOnChangeIfRequired(credentialType, credential, userId, isLockTiedToParent);
     }
 
     /**
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 1f1ba20..5d667b6 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -35,7 +35,6 @@
 import android.media.projection.MediaProjectionManager;
 import android.os.Binder;
 import android.os.Build;
-import android.os.Build.VERSION_CODES;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -63,7 +62,7 @@
  */
 public final class MediaProjectionManagerService extends SystemService
         implements Watchdog.Monitor {
-    private static final boolean REQUIRE_FG_SERVICE_FOR_PROJECTION = false;
+    private static final boolean REQUIRE_FG_SERVICE_FOR_PROJECTION = true;
     private static final String TAG = "MediaProjectionManagerService";
 
     private final Object mLock = new Object(); // Protects the list of media projections
@@ -146,7 +145,7 @@
                 return;
             }
 
-            if (mProjectionGrant.targetSdkVersion < VERSION_CODES.Q) {
+            if (!mProjectionGrant.requiresForegroundService()) {
                 return;
             }
 
@@ -294,7 +293,8 @@
                     throw new IllegalArgumentException("No package matching :" + packageName);
                 }
 
-                projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion);
+                projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
+                        ai.isPrivilegedApp());
                 if (isPermanentGrant) {
                     mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
                             projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
@@ -396,19 +396,22 @@
         public final int uid;
         public final String packageName;
         public final UserHandle userHandle;
-        public final int targetSdkVersion;
+        private final int mTargetSdkVersion;
+        private final boolean mIsPrivileged;
 
         private IMediaProjectionCallback mCallback;
         private IBinder mToken;
         private IBinder.DeathRecipient mDeathEater;
         private int mType;
 
-        MediaProjection(int type, int uid, String packageName, int targetSdkVersion) {
+        MediaProjection(int type, int uid, String packageName, int targetSdkVersion,
+                boolean isPrivileged) {
             mType = type;
             this.uid = uid;
             this.packageName = packageName;
             userHandle = new UserHandle(UserHandle.getUserId(uid));
-            this.targetSdkVersion = targetSdkVersion;
+            mTargetSdkVersion = targetSdkVersion;
+            mIsPrivileged = isPrivileged;
         }
 
         @Override // Binder call
@@ -466,7 +469,7 @@
                 }
 
                 if (REQUIRE_FG_SERVICE_FOR_PROJECTION
-                        && targetSdkVersion >= Build.VERSION_CODES.Q
+                        && requiresForegroundService()
                         && !mActivityManagerInternal.hasRunningForegroundService(
                                 uid, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)) {
                     throw new SecurityException("Media projections require a foreground service"
@@ -531,6 +534,10 @@
             return new MediaProjectionInfo(packageName, userHandle);
         }
 
+        boolean requiresForegroundService() {
+            return mTargetSdkVersion >= Build.VERSION_CODES.Q && !mIsPrivileged;
+        }
+
         public void dump(PrintWriter pw) {
             pw.println("(" + packageName + ", uid=" + uid + "): " + typeToString(mType));
         }
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java
similarity index 99%
rename from core/java/com/android/internal/net/NetworkStatsFactory.java
rename to services/core/java/com/android/server/net/NetworkStatsFactory.java
index f848346..af299cf 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.net;
+package com.android.server.net;
 
 import static android.net.NetworkStats.SET_ALL;
 import static android.net.NetworkStats.TAG_ALL;
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 205ddb0..1559911 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -130,7 +130,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.net.NetworkStatsFactory;
 import com.android.internal.net.VpnInfo;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index bfab85b..f2e56b5 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.notification;
 
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.Notification.CATEGORY_CALL;
 import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
@@ -107,6 +108,7 @@
 import android.app.NotificationManager;
 import android.app.NotificationManager.Policy;
 import android.app.PendingIntent;
+import android.app.Person;
 import android.app.StatusBarManager;
 import android.app.UriGrantsManager;
 import android.app.admin.DeviceAdminInfo;
@@ -4762,11 +4764,36 @@
     /**
      * Updates the flags for this notification to reflect whether it is a bubble or not.
      */
-    private void flagNotificationForBubbles(NotificationRecord r, String pkg, int userId) {
+    private void flagNotificationForBubbles(NotificationRecord r, String pkg, int userId,
+            NotificationRecord oldRecord) {
         Notification notification = r.getNotification();
-        boolean canBubble = mPreferencesHelper.areBubblesAllowed(pkg, userId)
+
+        // Does the app want to bubble & have permission to bubble?
+        boolean canBubble = notification.getBubbleMetadata() != null
+                && mPreferencesHelper.areBubblesAllowed(pkg, userId)
                 && r.getChannel().canBubble();
-        if (notification.getBubbleMetadata() != null && canBubble) {
+
+        // Is the app in the foreground?
+        final boolean appIsForeground =
+                mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
+
+        // Is the notification something we'd allow to bubble?
+        // A call with a foreground service + person
+        ArrayList<Person> peopleList = notification.extras != null
+                ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
+                : null;
+        boolean isForegroundCall = CATEGORY_CALL.equals(notification.category)
+                && (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
+        // OR message style (which always has a person)
+        Class<? extends Notification.Style> style = notification.getNotificationStyle();
+        boolean isMessageStyle = Notification.MessagingStyle.class.equals(style);
+        boolean notificationAppropriateToBubble = isMessageStyle
+                || (peopleList != null && !peopleList.isEmpty() && isForegroundCall);
+        // OR something that was previously a bubble & still exists
+        boolean bubbleUpdate = oldRecord != null
+                && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0;
+
+        if (canBubble && (notificationAppropriateToBubble || appIsForeground || bubbleUpdate)) {
             notification.flags |= FLAG_BUBBLE;
         } else {
             notification.flags &= ~FLAG_BUBBLE;
@@ -5129,7 +5156,7 @@
                 final String tag = n.getTag();
 
                 // We need to fix the notification up a little for bubbles
-                flagNotificationForBubbles(r, pkg, callingUid);
+                flagNotificationForBubbles(r, pkg, callingUid, old);
 
                 // Handle grouped notifications and bail out early if we
                 // can to avoid extracting signals.
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index a3e90dc..f34b2cb 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -98,7 +98,7 @@
     private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
     private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
     @VisibleForTesting
-    static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = false;
+    static final boolean DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS = true;
     private static final boolean DEFAULT_SHOW_BADGE = true;
     private static final boolean DEFAULT_ALLOW_BUBBLE = true;
     private static final boolean DEFAULT_OEM_LOCKED_IMPORTANCE  = false;
@@ -132,7 +132,7 @@
     private SparseBooleanArray mBadgingEnabled;
     private SparseBooleanArray mBubblesEnabled;
     private boolean mAreChannelsBypassingDnd;
-    private boolean mHideSilentStatusBarIcons;
+    private boolean mHideSilentStatusBarIcons = DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS;
 
     public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
             ZenModeHelper zenHelper) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 6e98d6e..51d5acc 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -374,7 +374,7 @@
                     synchronized (mLock) {
                         final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
                                 false);
-                        if (pi != null) {
+                        if (pi != null && !pi.applicationInfo.isInstantApp()) {
                             mPackageManager.cachePackageInfo(packageName, userId, pi);
                             if (pi.isOverlayPackage()) {
                                 mImpl.onOverlayPackageAdded(packageName, userId);
@@ -397,7 +397,7 @@
                     synchronized (mLock) {
                         final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
                                 false);
-                        if (pi != null) {
+                        if (pi != null && pi.applicationInfo.isInstantApp()) {
                             mPackageManager.cachePackageInfo(packageName, userId, pi);
                             if (pi.isOverlayPackage()) {
                                 mImpl.onOverlayPackageChanged(packageName, userId);
@@ -438,7 +438,7 @@
                     synchronized (mLock) {
                         final PackageInfo pi = mPackageManager.getPackageInfo(packageName, userId,
                                 false);
-                        if (pi != null) {
+                        if (pi != null && !pi.applicationInfo.isInstantApp()) {
                             mPackageManager.cachePackageInfo(packageName, userId, pi);
                             if (pi.isOverlayPackage()) {
                                 mImpl.onOverlayPackageReplaced(packageName, userId);
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 4b46374..7f346f5 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -148,51 +148,6 @@
         }
     }
 
-    int performDexOpt(SharedLibraryInfo info, String[] instructionSets, DexoptOptions options) {
-        String classLoaderContext = DexoptUtils.getClassLoaderContext(info);
-        final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
-        String compilerFilter = PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
-                PackageManagerService.REASON_SHARED);
-        int result = DEX_OPT_SKIPPED;
-        for (String instructionSet : dexCodeInstructionSets) {
-            int dexoptNeeded = getDexoptNeeded(
-                        info.getPath(), instructionSet, compilerFilter,
-                        classLoaderContext, false /* newProfile */,
-                        false /* downgrade */);
-            if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) {
-                continue;
-            }
-            // Special string recognized by installd.
-            final String packageName = "*";
-            final String outputPath = null;
-            int dexFlags = DEXOPT_PUBLIC
-                    | (options.isBootComplete() ? DEXOPT_BOOTCOMPLETE : 0)
-                    | (options.isDexoptIdleBackgroundJob() ? DEXOPT_IDLE_BACKGROUND_JOB : 0);
-            dexFlags = adjustDexoptFlags(dexFlags);
-            final String uuid = StorageManager.UUID_SYSTEM;
-            final String seInfo = null;
-            final int targetSdkVersion = 0;  // Builtin libraries targets the system's SDK version
-            try {
-                mInstaller.dexopt(info.getPath(), Process.SYSTEM_UID, packageName,
-                        instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter,
-                        uuid, classLoaderContext, seInfo, false /* downgrade */,
-                        targetSdkVersion, /*profileName*/ null, /*dexMetadataPath*/ null,
-                        getReasonName(options.getCompilationReason()));
-                // 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 != DEX_OPT_FAILED) {
-                    result = DEX_OPT_PERFORMED;
-                }
-            } catch (InstallerException e) {
-                Slog.w(TAG, "Failed to dexopt", e);
-                result = DEX_OPT_FAILED;
-            }
-        }
-        return result;
-    }
-
     /**
      * Performs dexopt on all code paths of the given package.
      * It assumes the install lock is held.
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index d2a160b..3306ccd 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -498,6 +498,7 @@
             info.isStagedSessionReady = mStagedSessionReady;
             info.isStagedSessionFailed = mStagedSessionFailed;
             info.setStagedSessionErrorCode(mStagedSessionErrorCode, mStagedSessionErrorMessage);
+            info.updatedMillis = updatedMillis;
         }
         return info;
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 098225f..78aa5a0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -476,6 +476,7 @@
     static final int SCAN_AS_VENDOR = 1 << 20;
     static final int SCAN_AS_PRODUCT = 1 << 21;
     static final int SCAN_AS_PRODUCT_SERVICES = 1 << 22;
+    static final int SCAN_AS_ODM = 1 << 23;
 
     @IntDef(flag = true, prefix = { "SCAN_" }, value = {
             SCAN_NO_DEX,
@@ -594,6 +595,8 @@
 
     private static final String PRODUCT_SERVICES_OVERLAY_DIR = "/product_services/overlay";
 
+    private static final String ODM_OVERLAY_DIR = "/odm/overlay";
+
     /** Canonical intent used to identify what counts as a "web browser" app */
     private static final Intent sBrowserIntent;
     static {
@@ -2523,6 +2526,13 @@
                     | SCAN_AS_SYSTEM
                     | SCAN_AS_PRODUCT_SERVICES,
                     0);
+            scanDirTracedLI(new File(ODM_OVERLAY_DIR),
+                    mDefParseFlags
+                    | PackageParser.PARSE_IS_SYSTEM_DIR,
+                    scanFlags
+                    | SCAN_AS_SYSTEM
+                    | SCAN_AS_ODM,
+                    0);
 
             mParallelPackageParserCallback.findStaticOverlayPackages();
 
@@ -3226,6 +3236,8 @@
         // once we have a booted system.
         mInstaller.setWarnIfHeld(mPackages);
 
+        PackageParser.readConfigUseRoundIcon(mContext.getResources());
+
         mServiceStartWithDelay = SystemClock.uptimeMillis() + (60 * 1000L);
 
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -4688,6 +4700,11 @@
                     if (ps == null || now - ps.lastUpdateTime < maxCachePeriod) {
                         continue;
                     }
+
+                    if (ps.pkg.isSystem()) {
+                        continue;
+                    }
+
                     if (packagesToDelete == null) {
                         packagesToDelete = new ArrayList<>();
                     }
@@ -5137,7 +5154,7 @@
                 continue;
             }
 
-            if (!ps.getUserState().get(userId).isAvailable(flags)) {
+            if (!ps.readUserState(userId).isAvailable(flags)) {
                 continue;
             }
 
@@ -6968,8 +6985,7 @@
         }
         final PackageSetting ps = mSettings.mPackages.get(mInstantAppInstallerActivity.packageName);
         if (ps == null
-                || ps.getUserState().get(userId) == null
-                || !ps.getUserState().get(userId).isEnabled(mInstantAppInstallerActivity, 0)) {
+                || !ps.readUserState(userId).isEnabled(mInstantAppInstallerActivity, 0)) {
             return result;
         }
         final ResolveInfo ephemeralInstaller = new ResolveInfo(mInstantAppInstallerInfo);
@@ -9569,7 +9585,7 @@
                             mDexManager.getPackageUseInfoOrDefault(depPackage.packageName),
                             libraryOptions);
                 } else {
-                    pdo.performDexOpt(info, instructionSets, libraryOptions);
+                    // TODO(ngeoffray): Support dexopting system shared libraries.
                 }
             }
         }
@@ -10399,6 +10415,7 @@
      * <li>{@link #SCAN_AS_PRODUCT_SERVICES}</li>
      * <li>{@link #SCAN_AS_INSTANT_APP}</li>
      * <li>{@link #SCAN_AS_VIRTUAL_PRELOAD}</li>
+     * <li>{@link #SCAN_AS_ODM}</li>
      * </ul>
      */
     private @ScanFlags int adjustScanFlags(@ScanFlags int scanFlags,
@@ -10435,6 +10452,10 @@
                     & ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES) != 0) {
                 scanFlags |= SCAN_AS_PRODUCT_SERVICES;
             }
+            if ((systemPkgSetting.pkgPrivateFlags
+                    & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) {
+                scanFlags |= SCAN_AS_ODM;
+            }
         }
         if (pkgSetting != null) {
             final int userId = ((user == null) ? 0 : user.getIdentifier());
@@ -11206,6 +11227,10 @@
             pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES;
         }
 
+        if ((scanFlags & SCAN_AS_ODM) != 0) {
+            pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_ODM;
+        }
+
         // Check if the package is signed with the same key as the platform package.
         if (PLATFORM_PACKAGE_NAME.equals(pkg.packageName) ||
                 (platformPkg != null && compareSignatures(
@@ -11532,17 +11557,44 @@
                                 " is static but not pre-installed.");
                     }
 
-                    // The only case where we allow installation of a non-system overlay is when
-                    // its signature is signed with the platform certificate.
-                    PackageSetting platformPkgSetting = mSettings.getPackageLPr("android");
-                    if ((platformPkgSetting.signatures.mSigningDetails
-                            != PackageParser.SigningDetails.UNKNOWN)
-                            && (compareSignatures(
-                                    platformPkgSetting.signatures.mSigningDetails.signatures,
-                                    pkg.mSigningDetails.signatures)
-                                            != PackageManager.SIGNATURE_MATCH)) {
-                        throw new PackageManagerException("Overlay " + pkg.packageName +
-                                " must be signed with the platform certificate.");
+                    // A non-preloaded overlay packages must have targetSdkVersion >= Q, or be
+                    // signed with the platform certificate. Check this in increasing order of
+                    // computational cost.
+                    if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q) {
+                        final PackageSetting platformPkgSetting =
+                                mSettings.getPackageLPr("android");
+                        if ((platformPkgSetting.signatures.mSigningDetails
+                                    != PackageParser.SigningDetails.UNKNOWN)
+                                && (compareSignatures(
+                                        platformPkgSetting.signatures.mSigningDetails.signatures,
+                                        pkg.mSigningDetails.signatures)
+                                    != PackageManager.SIGNATURE_MATCH)) {
+                            throw new PackageManagerException("Overlay " + pkg.packageName
+                                    + " must target Q or later, "
+                                    + "or be signed with the platform certificate");
+                        }
+                    }
+
+                    // A non-preloaded overlay package, without <overlay android:targetName>, will
+                    // only be used if it is signed with the same certificate as its target. If the
+                    // target is already installed, check this here to augment the last line of
+                    // defence which is OMS.
+                    if (pkg.mOverlayTargetName == null) {
+                        final PackageSetting targetPkgSetting =
+                                mSettings.getPackageLPr(pkg.mOverlayTarget);
+                        if (targetPkgSetting != null) {
+                            if ((targetPkgSetting.signatures.mSigningDetails
+                                        != PackageParser.SigningDetails.UNKNOWN)
+                                    && (compareSignatures(
+                                            targetPkgSetting.signatures.mSigningDetails.signatures,
+                                            pkg.mSigningDetails.signatures)
+                                        != PackageManager.SIGNATURE_MATCH)) {
+                                throw new PackageManagerException("Overlay " + pkg.packageName
+                                        + " and target " + pkg.mOverlayTarget + " signed with"
+                                        + " different certificates, and the overlay lacks"
+                                        + " <overlay android:targetName>");
+                            }
+                        }
                     }
                 }
             }
@@ -12128,6 +12180,8 @@
             codeRoot = Environment.getProductDirectory();
         } else if (FileUtils.contains(Environment.getProductServicesDirectory(), codePath)) {
             codeRoot = Environment.getProductServicesDirectory();
+        } else if (FileUtils.contains(Environment.getOdmDirectory(), codePath)) {
+            codeRoot = Environment.getOdmDirectory();
         } else {
             // Unrecognized code path; take its top real segment as the apk root:
             // e.g. /something/app/blah.apk => /something
@@ -14849,7 +14903,6 @@
                         }
                     }
 
-                    // TODO(ruhler) b/112431924: What user? Test for multi-user.
                     Intent enableRollbackIntent = new Intent(Intent.ACTION_PACKAGE_ENABLE_ROLLBACK);
                     enableRollbackIntent.putExtra(
                             PackageManagerInternal.EXTRA_ENABLE_ROLLBACK_TOKEN,
@@ -14871,7 +14924,7 @@
                     // it will not miss the broadcast.
                     enableRollbackIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
 
-                    mContext.sendOrderedBroadcastAsUser(enableRollbackIntent, getUser(),
+                    mContext.sendOrderedBroadcastAsUser(enableRollbackIntent, UserHandle.SYSTEM,
                             android.Manifest.permission.PACKAGE_ROLLBACK_AGENT,
                             new BroadcastReceiver() {
                                 @Override
@@ -16381,7 +16434,6 @@
                                         sharedLibLatestVersionSetting);
                             }
                         }
-                        prepareScanResultLocked(result);
                     }
                 } catch (PackageManagerException e) {
                     request.installResult.setError("Scanning Failed.", e);
@@ -17217,13 +17269,15 @@
                     final boolean oem = isOemApp(oldPackage);
                     final boolean vendor = isVendorApp(oldPackage);
                     final boolean product = isProductApp(oldPackage);
+                    final boolean odm = isOdmApp(oldPackage);
                     final @ParseFlags int systemParseFlags = parseFlags;
                     final @ScanFlags int systemScanFlags = scanFlags
                             | SCAN_AS_SYSTEM
                             | (privileged ? SCAN_AS_PRIVILEGED : 0)
                             | (oem ? SCAN_AS_OEM : 0)
                             | (vendor ? SCAN_AS_VENDOR : 0)
-                            | (product ? SCAN_AS_PRODUCT : 0);
+                            | (product ? SCAN_AS_PRODUCT : 0)
+                            | (odm ? SCAN_AS_ODM : 0);
 
                     if (DEBUG_INSTALL) {
                         Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
@@ -17554,6 +17608,10 @@
                 & ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES) != 0;
     }
 
+    private static boolean isOdmApp(PackageParser.Package pkg) {
+        return (pkg.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0;
+    }
+
     private static boolean hasDomainURLs(PackageParser.Package pkg) {
         return (pkg.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0;
     }
@@ -18328,6 +18386,15 @@
         return false;
     }
 
+    static boolean locationIsOdm(String path) {
+        try {
+            return path.startsWith(Environment.getOdmDirectory().getCanonicalPath() + "/");
+        } catch (IOException e) {
+            Slog.e(TAG, "Unable to access code path " + path);
+        }
+        return false;
+    }
+
     /*
      * Tries to delete system package.
      */
@@ -18441,6 +18508,9 @@
         if (locationIsProductServices(codePathString)) {
             scanFlags |= SCAN_AS_PRODUCT_SERVICES;
         }
+        if (locationIsOdm(codePathString)) {
+            scanFlags |= SCAN_AS_ODM;
+        }
 
         final File codePath = new File(codePathString);
         final PackageParser.Package pkg =
@@ -20843,6 +20913,7 @@
                 mContext.getContentResolver(),
                 android.provider.Settings.Global.COMPATIBILITY_MODE, 1) == 1;
         PackageParser.setCompatibilityModeEnabled(compatibilityModeEnabled);
+
         if (DEBUG_SETTINGS) {
             Log.d(TAG, "compatibility mode:" + compatibilityModeEnabled);
         }
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 2c2cc7e..ead09b4 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -152,6 +152,10 @@
         return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES) != 0;
     }
 
+    public boolean isOdm() {
+        return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0;
+    }
+
     public boolean isSystem() {
         return (pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0;
     }
diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java
index fbf5439..a24818f 100644
--- a/services/core/java/com/android/server/pm/SettingBase.java
+++ b/services/core/java/com/android/server/pm/SettingBase.java
@@ -64,6 +64,7 @@
                 | ApplicationInfo.PRIVATE_FLAG_VENDOR
                 | ApplicationInfo.PRIVATE_FLAG_PRODUCT
                 | ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES
-                | ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER);
+                | ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER
+                | ApplicationInfo.PRIVATE_FLAG_ODM);
     }
 }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6a1f223..4f81fd9 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -60,6 +60,7 @@
 import android.os.PatternMatcher;
 import android.os.PersistableBundle;
 import android.os.Process;
+import android.os.SELinux;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -776,7 +777,8 @@
                 | ApplicationInfo.PRIVATE_FLAG_OEM
                 | ApplicationInfo.PRIVATE_FLAG_VENDOR
                 | ApplicationInfo.PRIVATE_FLAG_PRODUCT
-                | ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES);
+                | ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES
+                | ApplicationInfo.PRIVATE_FLAG_ODM);
         pkgSetting.pkgFlags |= pkgFlags & ApplicationInfo.FLAG_SYSTEM;
         pkgSetting.pkgPrivateFlags |=
                 pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
@@ -788,6 +790,8 @@
                 pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT;
         pkgSetting.pkgPrivateFlags |=
                 pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES;
+        pkgSetting.pkgPrivateFlags |=
+                pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_ODM;
         pkgSetting.primaryCpuAbiString = primaryCpuAbi;
         pkgSetting.secondaryCpuAbiString = secondaryCpuAbi;
         if (childPkgNames != null) {
@@ -2650,6 +2654,24 @@
     }
 
     void writePackageListLPr(int creatingUserId) {
+        String filename = mPackageListFilename.getAbsolutePath();
+        String ctx = SELinux.fileSelabelLookup(filename);
+        if (ctx == null) {
+            Slog.wtf(TAG, "Failed to get SELinux context for " +
+                mPackageListFilename.getAbsolutePath());
+        }
+
+        if (!SELinux.setFSCreateContext(ctx)) {
+            Slog.wtf(TAG, "Failed to set packages.list SELinux context");
+        }
+        try {
+            writePackageListLPrInternal(creatingUserId);
+        } finally {
+            SELinux.setFSCreateContext(null);
+        }
+    }
+
+    private void writePackageListLPrInternal(int creatingUserId) {
         // Only derive GIDs for active users (not dying)
         final List<UserInfo> users = UserManagerService.getInstance().getUsers(true);
         int[] userIds = new int[users.size()];
@@ -4401,6 +4423,7 @@
             ApplicationInfo.PRIVATE_FLAG_PRODUCT, "PRODUCT",
             ApplicationInfo.PRIVATE_FLAG_PRODUCT_SERVICES, "PRODUCT_SERVICES",
             ApplicationInfo.PRIVATE_FLAG_VIRTUAL_PRELOAD, "VIRTUAL_PRELOAD",
+            ApplicationInfo.PRIVATE_FLAG_ODM, "ODM",
     };
 
     void dumpVersionLPr(IndentingPrintWriter pw) {
diff --git a/services/core/java/com/android/server/testharness/TestHarnessModeService.java b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
index 9f9b797..4f11887 100644
--- a/services/core/java/com/android/server/testharness/TestHarnessModeService.java
+++ b/services/core/java/com/android/server/testharness/TestHarnessModeService.java
@@ -71,7 +71,6 @@
     private static final String TEST_HARNESS_MODE_PROPERTY = "persist.sys.test_harness";
 
     private PersistentDataBlockManagerInternal mPersistentDataBlockManagerInternal;
-    private boolean mShouldSetUpTestHarnessMode;
 
     public TestHarnessModeService(Context context) {
         super(context);
@@ -89,9 +88,8 @@
                 setUpTestHarnessMode();
                 break;
             case PHASE_BOOT_COMPLETED:
-                disableAutoSync();
-                configureSettings();
-                showNotification();
+                completeTestHarnessModeSetup();
+                showNotificationIfEnabled();
                 break;
         }
         super.onBootPhase(phase);
@@ -104,47 +102,45 @@
             // There's no data to apply, so leave it as-is.
             return;
         }
-        PersistentData persistentData;
-        try {
-            persistentData = PersistentData.fromBytes(testHarnessModeData);
-        } catch (SetUpTestHarnessModeException e) {
-            Slog.e(TAG, "Failed to set up Test Harness Mode. Bad data.", e);
-            return;
-        } finally {
-            // Clear out the Test Harness Mode data. It's now in memory if successful or we should
-            // skip setting up.
-            getPersistentDataBlock().clearTestHarnessModeData();
-        }
-        mShouldSetUpTestHarnessMode = true;
-        setUpAdb(persistentData);
+        // If there is data, we should set the device as provisioned, so that we skip the setup
+        // wizard.
         setDeviceProvisioned();
+        SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, "1");
     }
 
-    private void setUpAdb(PersistentData persistentData) {
-        ContentResolver cr = getContext().getContentResolver();
-
-        // Disable the TTL for ADB keys before enabling ADB
-        Settings.Global.putLong(cr, Settings.Global.ADB_ALLOWED_CONNECTION_TIME, 0);
-
-        SystemProperties.set(TEST_HARNESS_MODE_PROPERTY, "1");
-        writeAdbKeysFile(persistentData);
+    private void completeTestHarnessModeSetup() {
+        Slog.d(TAG, "Completing Test Harness Mode setup.");
+        byte[] testHarnessModeData = getPersistentDataBlock().getTestHarnessModeData();
+        if (testHarnessModeData == null || testHarnessModeData.length == 0) {
+            // There's no data to apply, so leave it as-is.
+            return;
+        }
+        try {
+            setUpAdbFiles(PersistentData.fromBytes(testHarnessModeData));
+            disableAutoSync();
+            configureSettings();
+        } catch (SetUpTestHarnessModeException e) {
+            Slog.e(TAG, "Failed to set up Test Harness Mode. Bad data.", e);
+        } finally {
+            // Clear out the Test Harness Mode data so that we don't repeat the setup. If it failed
+            // to set up, then retrying without enabling Test Harness Mode should allow it to boot.
+            // If we succeeded setting up, we shouldn't be re-applying the THM steps every boot
+            // anyway.
+            getPersistentDataBlock().clearTestHarnessModeData();
+        }
     }
 
     private void disableAutoSync() {
-        if (!mShouldSetUpTestHarnessMode) {
-            return;
-        }
         UserInfo primaryUser = UserManager.get(getContext()).getPrimaryUser();
         ContentResolver
             .setMasterSyncAutomaticallyAsUser(false, primaryUser.getUserHandle().getIdentifier());
     }
 
     private void configureSettings() {
-        if (!mShouldSetUpTestHarnessMode) {
-            return;
-        }
         ContentResolver cr = getContext().getContentResolver();
 
+        // Disable the TTL for ADB keys before enabling ADB
+        Settings.Global.putLong(cr, Settings.Global.ADB_ALLOWED_CONNECTION_TIME, 0);
         Settings.Global.putInt(cr, Settings.Global.ADB_ENABLED, 1);
         Settings.Global.putInt(cr, Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 1);
         Settings.Global.putInt(cr, Settings.Global.PACKAGE_VERIFIER_ENABLE, 0);
@@ -155,7 +151,7 @@
         Settings.Global.putInt(cr, Settings.Global.OTA_DISABLE_AUTOMATIC_UPDATE, 1);
     }
 
-    private void writeAdbKeysFile(PersistentData persistentData) {
+    private void setUpAdbFiles(PersistentData persistentData) {
         AdbManagerInternal adbManager = LocalServices.getService(AdbManagerInternal.class);
 
         writeBytesToFile(persistentData.mAdbKeys, adbManager.getAdbKeysFile().toPath());
@@ -189,7 +185,7 @@
                 UserHandle.USER_CURRENT);
     }
 
-    private void showNotification() {
+    private void showNotificationIfEnabled() {
         if (!SystemProperties.getBoolean(TEST_HARNESS_MODE_PROPERTY, false)) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 7c12c1e..3b358e8 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -949,7 +949,8 @@
         final boolean callingUidHasAnyVisibleWindow =
                 mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid);
         final boolean isCallingUidForeground = callingUidHasAnyVisibleWindow
-                || callingUidProcState == ActivityManager.PROCESS_STATE_TOP;
+                || callingUidProcState == ActivityManager.PROCESS_STATE_TOP
+                || callingUidProcState == ActivityManager.PROCESS_STATE_BOUND_TOP;
         final boolean isCallingUidPersistentSystemProcess = (callingUid == Process.SYSTEM_UID)
                 || callingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI;
         if (isCallingUidForeground || isCallingUidPersistentSystemProcess) {
@@ -980,6 +981,11 @@
             if (isRealCallingUidPersistentSystemProcess && allowBackgroundActivityStart) {
                 return false;
             }
+            // don't abort if the realCallingUid is an associated companion app
+            if (mService.isAssociatedCompanionApp(UserHandle.getUserId(realCallingUid),
+                    realCallingUid)) {
+                return false;
+            }
         }
         // If we don't have callerApp at this point, no caller was provided to startActivity().
         // That's the case for PendingIntent-based starts, since the creator's process might not be
@@ -1025,7 +1031,7 @@
         }
         // don't abort if the callingPackage has companion device
         final int callingUserId = UserHandle.getUserId(callingUid);
-        if (mService.isAssociatedCompanionApp(callingUserId, callingPackage)) {
+        if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) {
             return false;
         }
         // don't abort if the callingPackage is temporarily whitelisted
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 9a8824f..b64abdb 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -443,8 +443,8 @@
     // VoiceInteractionManagerService
     ComponentName mActiveVoiceInteractionServiceComponent;
 
-    // A map userId and all its companion app packages
-    private final Map<Integer, Set<String>> mCompanionAppPackageMap = new ArrayMap<>();
+    // A map userId and all its companion app uids
+    private final Map<Integer, Set<Integer>> mCompanionAppUidsMap = new ArrayMap<>();
 
     VrController mVrController;
     KeyguardController mKeyguardController;
@@ -2084,7 +2084,7 @@
         synchronized (mGlobalLock) {
             final long ident = Binder.clearCallingIdentity();
             try {
-                getRecentTasks().removeAllVisibleTasks();
+                getRecentTasks().removeAllVisibleTasks(mAmInternal.getCurrentUserId());
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -5812,15 +5812,10 @@
     }
 
     WindowProcessController getProcessController(int pid, int uid) {
-        final ArrayMap<String, SparseArray<WindowProcessController>> pmap = mProcessNames.getMap();
-        for (int i = pmap.size()-1; i >= 0; i--) {
-            final SparseArray<WindowProcessController> procs = pmap.valueAt(i);
-            for (int j = procs.size() - 1; j >= 0; j--) {
-                final WindowProcessController proc = procs.valueAt(j);
-                if (UserHandle.isApp(uid) && proc.getPid() == pid && proc.mUid == uid) {
-                    return proc;
-                }
-            }
+        final WindowProcessController proc = mPidMap.get(pid);
+        if (proc == null) return null;
+        if (UserHandle.isApp(uid) && proc.mUid == uid) {
+            return proc;
         }
         return null;
     }
@@ -5912,12 +5907,12 @@
         }
     }
 
-    boolean isAssociatedCompanionApp(int userId, String packageName) {
-        final Set<String> allPackages = mCompanionAppPackageMap.get(userId);
-        if (allPackages == null) {
+    boolean isAssociatedCompanionApp(int userId, int uid) {
+        final Set<Integer> allUids = mCompanionAppUidsMap.get(userId);
+        if (allUids == null) {
             return false;
         }
-        return allPackages.contains(packageName);
+        return allUids.contains(uid);
     }
 
     final class H extends Handler {
@@ -7296,13 +7291,16 @@
 
         @Override
         public void setCompanionAppPackages(int userId, Set<String> companionAppPackages) {
-            // Deep copy all content to make sure we do not rely on the source
-            final Set<String> result = new HashSet<>();
+            // Translate package names into UIDs
+            final Set<Integer> result = new HashSet<>();
             for (String pkg : companionAppPackages) {
-                result.add(pkg);
+                final int uid = getPackageManagerInternalLocked().getPackageUid(pkg, 0, userId);
+                if (uid >= 0) {
+                    result.add(uid);
+                }
             }
             synchronized (mGlobalLock) {
-                mCompanionAppPackageMap.put(userId, result);
+                mCompanionAppUidsMap.put(userId, result);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index f46835e..6b500967 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -6,6 +6,7 @@
 
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS;
 
 import android.os.Debug;
 import android.os.IBinder;
@@ -232,6 +233,11 @@
         }
     }
 
+    @Override
+    public void onPointerDownOutsideFocus(IBinder touchedToken) {
+        mService.mH.obtainMessage(ON_POINTER_DOWN_OUTSIDE_FOCUS, touchedToken).sendToTarget();
+    }
+
     /** Waits until the built-in input devices have been configured. */
     public boolean waitForInputDevicesReady(long timeoutMillis) {
         synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index f85fdb6..d3dba90 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -63,6 +63,7 @@
     // When true, need to call updateInputWindowsLw().
     private boolean mUpdateInputWindowsNeeded = true;
     private boolean mUpdateInputWindowsPending;
+    private boolean mApplyImmediately;
 
     // Currently focused input window handle.
     private InputWindowHandle mFocusedInputWindowHandle;
@@ -152,7 +153,7 @@
         mService = service;
         mDisplayContent = mService.mRoot.getDisplayContent(displayId);
         mDisplayId = displayId;
-        mInputTransaction = mDisplayContent.getPendingTransaction();
+        mInputTransaction = mService.mTransactionFactory.make();
         mHandler = AnimationThread.getHandler();
         mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer();
     }
@@ -319,6 +320,14 @@
         }
     }
 
+    void updateInputWindowsImmediately() {
+        if (mUpdateInputWindowsPending) {
+            mApplyImmediately = true;
+            mUpdateInputWindows.run();
+            mApplyImmediately = false;
+        }
+    }
+
     /* Called when the current input focus changes.
      * Layer assignment is assumed to be complete by the time this is called.
      */
@@ -433,7 +442,12 @@
                 wallpaperInputConsumer.show(mInputTransaction, 0);
             }
 
-            mDisplayContent.scheduleAnimation();
+            if (mApplyImmediately) {
+                mInputTransaction.apply();
+            } else {
+                mDisplayContent.getPendingTransaction().merge(mInputTransaction);
+                mDisplayContent.scheduleAnimation();
+            }
 
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 24b0213..d6c7b21 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -653,10 +653,10 @@
         }
     }
 
-    void removeAllVisibleTasks() {
+    void removeAllVisibleTasks(int userId) {
         for (int i = mTasks.size() - 1; i >= 0; --i) {
             final TaskRecord tr = mTasks.get(i);
-            if (isVisibleRecentTask(tr)) {
+            if (tr.userId == userId && isVisibleRecentTask(tr)) {
                 mTasks.remove(i);
                 notifyTaskRemoved(tr, true /* wasTrimmed */, true /* killProcess */);
             }
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index f1560d9..144efb4 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -17,8 +17,6 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.AppOpsManager.OP_ASSIST_STRUCTURE;
-import static android.app.AppOpsManager.OP_NONE;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -34,25 +32,16 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS;
 
 import android.app.ActivityOptions;
-import android.app.AppOpsManager;
 import android.app.IAssistDataReceiver;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
-import android.os.Bundle;
-import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.Trace;
 import android.util.Slog;
 import android.view.IRecentsAnimationRunner;
 
-import com.android.server.LocalServices;
-import com.android.server.am.AssistDataRequester;
-import com.android.server.contentcapture.ContentCaptureManagerInternal;
 import com.android.server.wm.RecentsAnimationController.RecentsAnimationCallbacks;
 
-import java.util.List;
-
 /**
  * Manages the recents animation, including the reordering of the stacks for the transition and
  * cleanup. See {@link com.android.server.wm.RecentsAnimationController}.
@@ -70,7 +59,6 @@
     private final int mCallingPid;
 
     private int mTargetActivityType;
-    private AssistDataRequester mAssistDataRequester;
 
     // The stack to restore the target stack behind when the animation is finished
     private ActivityStack mRestoreTargetBehindStack;
@@ -135,9 +123,6 @@
 
         mWindowManager.deferSurfaceLayout();
         try {
-            // Kick off the assist data request in the background before showing the target activity
-            requestAssistData(recentsComponent, recentsUid, assistDataReceiver);
-
             if (hasExistingActivity) {
                 // Move the recents activity into place for the animation if it is not top most
                 mDefaultDisplay.moveStackBehindBottomMostVisibleStack(targetStack);
@@ -216,78 +201,12 @@
         }
     }
 
-    /**
-     * Requests assist data for the top visible activities.
-     */
-    private void requestAssistData(ComponentName recentsComponent, int recentsUid,
-            @Deprecated IAssistDataReceiver assistDataReceiver) {
-        final AppOpsManager appOpsManager = (AppOpsManager)
-                mService.mContext.getSystemService(Context.APP_OPS_SERVICE);
-        final List<IBinder> topActivities =
-                mService.mRootActivityContainer.getTopVisibleActivities();
-        final AssistDataRequester.AssistDataRequesterCallbacks assistDataCallbacks;
-        if (assistDataReceiver != null) {
-            assistDataCallbacks = new AssistDataReceiverProxy(assistDataReceiver,
-                    recentsComponent.getPackageName()) {
-                @Override
-                public void onAssistDataReceivedLocked(Bundle data, int activityIndex,
-                        int activityCount) {
-                    // Try to notify the intelligence service first
-                    final ContentCaptureManagerInternal imService =
-                            LocalServices.getService(ContentCaptureManagerInternal.class);
-                    final IBinder activityToken = topActivities.get(activityIndex);
-                    final ActivityRecord r = ActivityRecord.forTokenLocked(activityToken);
-                    if (r != null && (imService == null
-                            || !imService.sendActivityAssistData(r.mUserId, activityToken, data))) {
-                        // Otherwise, use the provided assist data receiver
-                        super.onAssistDataReceivedLocked(data, activityIndex, activityCount);
-                    }
-                }
-            };
-        } else {
-            final ContentCaptureManagerInternal imService =
-                    LocalServices.getService(ContentCaptureManagerInternal.class);
-            if (imService == null) {
-                // There is no intelligence service, so there is no point requesting assist data
-                return;
-            }
-
-            assistDataCallbacks = new AssistDataRequester.AssistDataRequesterCallbacks() {
-                @Override
-                public boolean canHandleReceivedAssistDataLocked() {
-                    return true;
-                }
-
-                @Override
-                public void onAssistDataReceivedLocked(Bundle data, int activityIndex,
-                        int activityCount) {
-                    // Try to notify the intelligence service
-                    final IBinder activityToken = topActivities.get(activityIndex);
-                    final ActivityRecord r = ActivityRecord.forTokenLocked(activityToken);
-                    if (r != null) {
-                        imService.sendActivityAssistData(r.mUserId, activityToken, data);
-                    }
-                }
-            };
-        }
-        mAssistDataRequester = new AssistDataRequester(mService.mContext, mWindowManager,
-                appOpsManager, assistDataCallbacks, this, OP_ASSIST_STRUCTURE, OP_NONE);
-        mAssistDataRequester.requestAutofillData(topActivities,
-                recentsUid, recentsComponent.getPackageName());
-    }
-
     private void finishAnimation(@RecentsAnimationController.ReorderMode int reorderMode) {
         synchronized (mService.mGlobalLock) {
             if (DEBUG) Slog.d(TAG, "onAnimationFinished(): controller="
                     + mWindowManager.getRecentsAnimationController()
                     + " reorderMode=" + reorderMode);
 
-            // Cancel the associated assistant data request
-            if (mAssistDataRequester != null) {
-                mAssistDataRequester.cancel();
-                mAssistDataRequester = null;
-            }
-
             // Unregister for stack order changes
             mDefaultDisplay.unregisterStackOrderChangedListener(this);
 
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index 4ff552e..79baab6 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -22,12 +22,9 @@
 import static android.view.PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
 import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
 
-import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
-
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.input.InputManager;
-import android.os.Handler;
 import android.view.MotionEvent;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
 
@@ -40,67 +37,15 @@
 public class TaskTapPointerEventListener implements PointerEventListener {
 
     private final Region mTouchExcludeRegion = new Region();
-    private final Region mTmpRegion = new Region();
     private final WindowManagerService mService;
     private final DisplayContent mDisplayContent;
-    private final Handler mHandler;
-    private final Runnable mMoveDisplayToTop;
     private final Rect mTmpRect = new Rect();
     private int mPointerIconType = TYPE_NOT_SPECIFIED;
-    private int mLastDownX;
-    private int mLastDownY;
 
     public TaskTapPointerEventListener(WindowManagerService service,
             DisplayContent displayContent) {
         mService = service;
         mDisplayContent = displayContent;
-        mHandler = new Handler(mService.mH.getLooper());
-        mMoveDisplayToTop = () -> {
-            int x;
-            int y;
-            synchronized (this) {
-                x = mLastDownX;
-                y = mLastDownY;
-            }
-            synchronized (mService.mGlobalLock) {
-                if (!mService.mPerDisplayFocusEnabled
-                        && mService.mRoot.getTopFocusedDisplayContent() != mDisplayContent
-                        && inputMethodWindowContains(x, y)) {
-                    // In a single focus system, if the input method window and the input method
-                    // target window are on the different displays, when the user is tapping on the
-                    // input method window, we don't move its display to top. Otherwise, the input
-                    // method target window will lose the focus.
-                    return;
-                }
-                final Region windowTapExcludeRegion = Region.obtain();
-                mDisplayContent.amendWindowTapExcludeRegion(windowTapExcludeRegion);
-                if (windowTapExcludeRegion.contains(x, y)) {
-                    windowTapExcludeRegion.recycle();
-                    // The user is tapping on the window tap exclude region. We don't move this
-                    // display to top. A window tap exclude region, for example, may be set by an
-                    // ActivityView, and the region would match the bounds of both the ActivityView
-                    // and the virtual display in it. In this case, we would take the tap that is on
-                    // the embedded virtual display instead of this display.
-                    return;
-                }
-                windowTapExcludeRegion.recycle();
-                WindowContainer parent = mDisplayContent.getParent();
-                if (parent != null && parent.getTopChild() != mDisplayContent) {
-                    parent.positionChildAt(WindowContainer.POSITION_TOP, mDisplayContent,
-                            true /* includingParents */);
-                    // For compatibility, only the topmost activity is allowed to be resumed for
-                    // pre-Q app. Ensure the topmost activities are resumed whenever a display is
-                    // moved to top.
-                    // TODO(b/123761773): Investigate whether we can move this into
-                    // RootActivityContainer#updateTopResumedActivityIfNeeded(). Currently, it is
-                    // risky to do so because it seems possible to resume activities as part of a
-                    // larger transaction and it's too early to resume based on current order
-                    // when performing updateTopResumedActivityIfNeeded().
-                    mDisplayContent.mAcitvityDisplay.ensureActivitiesVisible(null /* starting */,
-                            0 /* configChanges */, !PRESERVE_WINDOWS, true /* notifyClients */);
-                }
-            }
-        };
     }
 
     @Override
@@ -115,9 +60,6 @@
                         mService.mTaskPositioningController.handleTapOutsideTask(
                                 mDisplayContent, x, y);
                     }
-                    mLastDownX = x;
-                    mLastDownY = y;
-                    mHandler.post(mMoveDisplayToTop);
                 }
             }
             break;
@@ -178,17 +120,4 @@
            mTouchExcludeRegion.set(newRegion);
         }
     }
-
-    private int getDisplayId() {
-        return mDisplayContent.getDisplayId();
-    }
-
-    private boolean inputMethodWindowContains(int x, int y) {
-        final WindowState inputMethodWindow = mDisplayContent.mInputMethodWindow;
-        if (inputMethodWindow == null || !inputMethodWindow.isVisibleLw()) {
-            return false;
-        }
-        inputMethodWindow.getTouchableRegion(mTmpRegion);
-        return mTmpRegion.contains(x, y);
-    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 20d02ee..8dfb02e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -73,6 +73,7 @@
 import static com.android.server.LockGuard.INDEX_WINDOW;
 import static com.android.server.LockGuard.installLock;
 import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
@@ -4519,6 +4520,7 @@
         public static final int SET_RUNNING_REMOTE_ANIMATION = 59;
         public static final int ANIMATION_FAILSAFE = 60;
         public static final int RECOMPUTE_FOCUS = 61;
+        public static final int ON_POINTER_DOWN_OUTSIDE_FOCUS = 62;
 
         /**
          * Used to denote that an integer field in a message will not be used.
@@ -4910,6 +4912,13 @@
                     }
                     break;
                 }
+                case ON_POINTER_DOWN_OUTSIDE_FOCUS: {
+                    synchronized (mGlobalLock) {
+                        final IBinder touchedToken = (IBinder) msg.obj;
+                        onPointerDownOutsideFocusLocked(touchedToken);
+                    }
+                    break;
+                }
             }
             if (DEBUG_WINDOW_TRACE) {
                 Slog.v(TAG_WM, "handleMessage: exit");
@@ -7522,22 +7531,24 @@
 
     @Override
     public boolean injectInputAfterTransactionsApplied(InputEvent ev, int mode) {
-        boolean shouldWaitForAnimComplete = false;
+        boolean shouldWaitForAnimToComplete = false;
         if (ev instanceof KeyEvent) {
             KeyEvent keyEvent = (KeyEvent) ev;
-            shouldWaitForAnimComplete = keyEvent.getSource() == InputDevice.SOURCE_MOUSE
+            shouldWaitForAnimToComplete = keyEvent.getSource() == InputDevice.SOURCE_MOUSE
                     || keyEvent.getAction() == KeyEvent.ACTION_DOWN;
         } else if (ev instanceof MotionEvent) {
             MotionEvent motionEvent = (MotionEvent) ev;
-            shouldWaitForAnimComplete = motionEvent.getSource() == InputDevice.SOURCE_MOUSE
+            shouldWaitForAnimToComplete = motionEvent.getSource() == InputDevice.SOURCE_MOUSE
                     || motionEvent.getAction() == MotionEvent.ACTION_DOWN;
         }
 
-        if (shouldWaitForAnimComplete) {
+        if (shouldWaitForAnimToComplete) {
             waitForAnimationsToComplete();
 
             synchronized (mGlobalLock) {
                 mWindowPlacerLocked.performSurfacePlacementIfScheduled();
+                mRoot.forAllDisplays(displayContent ->
+                        displayContent.getInputMonitor().updateInputWindowsImmediately());
             }
 
             new SurfaceControl.Transaction().syncInputWindows().apply(true);
@@ -7569,4 +7580,37 @@
             mGlobalLock.notifyAll();
         }
     }
+
+    private void onPointerDownOutsideFocusLocked(IBinder touchedToken) {
+        final WindowState touchedWindow = windowForClientLocked(null, touchedToken, false);
+        if (touchedWindow == null) {
+            return;
+        }
+
+        final DisplayContent displayContent = touchedWindow.getDisplayContent();
+        if (displayContent == null) {
+            return;
+        }
+
+        if (!touchedWindow.canReceiveKeys()) {
+            // If the window that received the input event cannot receive keys, don't move the
+            // display it's on to the top since that window won't be able to get focus anyway.
+            return;
+        }
+
+        final WindowContainer parent = displayContent.getParent();
+        if (parent != null && parent.getTopChild() != displayContent) {
+            parent.positionChildAt(WindowContainer.POSITION_TOP, displayContent,
+                    true /* includingParents */);
+            // For compatibility, only the topmost activity is allowed to be resumed for pre-Q
+            // app. Ensure the topmost activities are resumed whenever a display is moved to top.
+            // TODO(b/123761773): Investigate whether we can move this into
+            // RootActivityContainer#updateTopResumedActivityIfNeeded(). Currently, it is risky
+            // to do so because it seems possible to resume activities as part of a larger
+            // transaction and it's too early to resume based on current order when performing
+            // updateTopResumedActivityIfNeeded().
+            displayContent.mAcitvityDisplay.ensureActivitiesVisible(null /* starting */,
+                    0 /* configChanges */, !PRESERVE_WINDOWS, true /* notifyClients */);
+        }
+    }
 }
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index a7423ea..e1318af 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -52,6 +52,7 @@
         "com_android_server_GraphicsStatsService.cpp",
         "com_android_server_am_AppCompactor.cpp",
         "onload.cpp",
+        ":lib_networkStatsFactory_native",
     ],
 
     include_dirs: [
@@ -151,3 +152,10 @@
         }
     }
 }
+
+filegroup {
+    name: "lib_networkStatsFactory_native",
+    srcs: [
+        "com_android_server_net_NetworkStatsFactory.cpp",
+    ],
+}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3d6c868..204a1ea 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -47,6 +47,7 @@
 
 #include <input/PointerController.h>
 #include <input/SpriteController.h>
+#include <ui/Region.h>
 
 #include <inputflinger/InputManager.h>
 
@@ -137,8 +138,6 @@
     jmethodID getAffineTransform;
 } gTouchCalibrationClassInfo;
 
-
-
 // --- Global functions ---
 
 template<typename T>
@@ -188,7 +187,6 @@
     return result;
 }
 
-
 // --- NativeInputManager ---
 
 class NativeInputManager : public virtual RefBase,
@@ -207,8 +205,12 @@
 
     void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
 
-    status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel, int32_t displayId);
+    status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel,
+            int32_t displayId);
+    status_t registerInputMonitor(JNIEnv* env, const sp<InputChannel>& inputChannel,
+            int32_t displayId, bool isGestureMonitor);
     status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);
+    status_t pilferPointers(const sp<IBinder>& token);
 
     void setInputWindows(JNIEnv* env, jobjectArray windowHandleObjArray, int32_t displayId);
     void setFocusedApplication(JNIEnv* env, int32_t displayId, jobject applicationHandleObj);
@@ -443,12 +445,24 @@
             inputChannel, displayId);
 }
 
+status_t NativeInputManager::registerInputMonitor(JNIEnv* /* env */,
+        const sp<InputChannel>& inputChannel, int32_t displayId, bool isGestureMonitor) {
+    ATRACE_CALL();
+    return mInputManager->getDispatcher()->registerInputMonitor(
+            inputChannel, displayId, isGestureMonitor);
+}
+
 status_t NativeInputManager::unregisterInputChannel(JNIEnv* /* env */,
         const sp<InputChannel>& inputChannel) {
     ATRACE_CALL();
     return mInputManager->getDispatcher()->unregisterInputChannel(inputChannel);
 }
 
+status_t NativeInputManager::pilferPointers(const sp<IBinder>& token) {
+    ATRACE_CALL();
+    return mInputManager->getDispatcher()->pilferPointers(token);
+}
+
 void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outConfig) {
     ATRACE_CALL();
     JNIEnv* env = jniEnv();
@@ -1396,7 +1410,6 @@
         throwInputChannelNotInitialized(env);
         return;
     }
-    bool monitor = inputChannel->getToken() == nullptr && displayId != ADISPLAY_ID_NONE;
 
     status_t status = im->registerInputChannel(env, inputChannel, displayId);
 
@@ -1407,10 +1420,33 @@
         return;
     }
 
-    // If inputWindowHandle is null and displayId >= 0, treat inputChannel as monitor.
-    if (!monitor) {
-        android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
-                handleInputChannelDisposed, im);
+    android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
+            handleInputChannelDisposed, im);
+}
+
+static void nativeRegisterInputMonitor(JNIEnv* env, jclass /* clazz */,
+        jlong ptr, jobject inputChannelObj, jint displayId, jboolean isGestureMonitor) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
+            inputChannelObj);
+    if (inputChannel == nullptr) {
+        throwInputChannelNotInitialized(env);
+        return;
+    }
+
+    if (displayId == ADISPLAY_ID_NONE) {
+        std::string message = "InputChannel used as a monitor must be associated with a display";
+        jniThrowRuntimeException(env, message.c_str());
+        return;
+    }
+
+    status_t status = im->registerInputMonitor(env, inputChannel, displayId, isGestureMonitor);
+
+    if (status) {
+        std::string message = StringPrintf("Failed to register input channel.  status=%d", status);
+        jniThrowRuntimeException(env, message.c_str());
+        return;
     }
 }
 
@@ -1435,6 +1471,13 @@
     }
 }
 
+static void nativePilferPointers(JNIEnv* env, jclass /* clazz */, jlong ptr, jobject tokenObj) {
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+    sp<IBinder> token = ibinderForJavaObject(env, tokenObj);
+    im->pilferPointers(token);
+}
+
+
 static void nativeSetInputFilterEnabled(JNIEnv* /* env */, jclass /* clazz */,
         jlong ptr, jboolean enabled) {
     NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1697,8 +1740,13 @@
     { "nativeRegisterInputChannel",
             "(JLandroid/view/InputChannel;I)V",
             (void*) nativeRegisterInputChannel },
+    { "nativeRegisterInputMonitor",
+            "(JLandroid/view/InputChannel;IZ)V",
+            (void*) nativeRegisterInputMonitor},
     { "nativeUnregisterInputChannel", "(JLandroid/view/InputChannel;)V",
             (void*) nativeUnregisterInputChannel },
+    { "nativePilferPointers", "(JLandroid/os/IBinder;)V",
+            (void*) nativePilferPointers },
     { "nativeSetInputFilterEnabled", "(JZ)V",
             (void*) nativeSetInputFilterEnabled },
     { "nativeInjectInputEvent", "(JLandroid/view/InputEvent;IIIII)I",
@@ -1792,7 +1840,7 @@
 
     GET_METHOD_ID(gServiceClassInfo.notifyInputChannelBroken, clazz,
             "notifyInputChannelBroken", "(Landroid/os/IBinder;)V");
-    
+
     GET_METHOD_ID(gServiceClassInfo.notifyFocusChanged, clazz,
             "notifyFocusChanged", "(Landroid/os/IBinder;Landroid/os/IBinder;)V");
 
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 0b47b29..f447f47 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -52,7 +52,7 @@
 static jmethodID method_reportSvStatus;
 static jmethodID method_reportAGpsStatus;
 static jmethodID method_reportNmea;
-static jmethodID method_setEngineCapabilities;
+static jmethodID method_setTopHalCapabilities;
 static jmethodID method_setGnssYearOfHardware;
 static jmethodID method_setGnssHardwareModelName;
 static jmethodID method_xtraDownloadRequest;
@@ -71,7 +71,7 @@
 static jmethodID method_reportNavigationMessages;
 static jmethodID method_reportLocationBatch;
 static jmethodID method_reportGnssServiceDied;
-static jmethodID method_setMeasurementCorrectionsCapabilities;
+static jmethodID method_setSubHalMeasurementCorrectionsCapabilities;
 static jmethodID method_correctionsGetLatitudeDegrees;
 static jmethodID method_correctionsGetLongitudeDegrees;
 static jmethodID method_correctionsGetAltitudeMeters;
@@ -754,7 +754,7 @@
     ALOGD("%s: capabilities=%du, hasSubHalCapabilityFlags=%d\n", __func__, capabilities,
         hasSubHalCapabilityFlags);
     JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_setEngineCapabilities, capabilities,
+    env->CallVoidMethod(mCallbacksObj, method_setTopHalCapabilities, capabilities,
             boolToJbool(hasSubHalCapabilityFlags));
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     return Void();
@@ -1237,7 +1237,8 @@
 Return<void> MeasurementCorrectionsCallback::setCapabilitiesCb(uint32_t capabilities) {
     ALOGD("%s: %du\n", __func__, capabilities);
     JNIEnv* env = getJniEnv();
-    env->CallVoidMethod(mCallbacksObj, method_setMeasurementCorrectionsCapabilities, capabilities);
+    env->CallVoidMethod(mCallbacksObj, method_setSubHalMeasurementCorrectionsCapabilities,
+                        capabilities);
     checkAndClearExceptionFromCallback(env, __FUNCTION__);
     return Void();
 }
@@ -1541,7 +1542,7 @@
     method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "(I[I[F[F[F[F)V");
     method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II[B)V");
     method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(J)V");
-    method_setEngineCapabilities = env->GetMethodID(clazz, "setEngineCapabilities", "(IZ)V");
+    method_setTopHalCapabilities = env->GetMethodID(clazz, "setTopHalCapabilities", "(IZ)V");
     method_setGnssYearOfHardware = env->GetMethodID(clazz, "setGnssYearOfHardware", "(I)V");
     method_setGnssHardwareModelName = env->GetMethodID(clazz, "setGnssHardwareModelName",
             "(Ljava/lang/String;)V");
@@ -1581,8 +1582,8 @@
             "(Ljava/lang/String;BLjava/lang/String;BLjava/lang/String;BZZ)V");
     method_isInEmergencySession = env->GetMethodID(clazz, "isInEmergencySession", "()Z");
 
-    method_setMeasurementCorrectionsCapabilities = env->GetMethodID(clazz,
-            "setMeasurementCorrectionsCapabilities", "(I)V");
+    method_setSubHalMeasurementCorrectionsCapabilities = env->GetMethodID(clazz,
+            "setSubHalMeasurementCorrectionsCapabilities", "(I)V");
 
     jclass measCorrClass = env->FindClass("android/location/GnssMeasurementCorrections");
     method_correctionsGetLatitudeDegrees = env->GetMethodID(
diff --git a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp b/services/core/jni/com_android_server_net_NetworkStatsFactory.cpp
similarity index 87%
rename from core/jni/com_android_internal_net_NetworkStatsFactory.cpp
rename to services/core/jni/com_android_server_net_NetworkStatsFactory.cpp
index 8259ffc..9cd743b 100644
--- a/core/jni/com_android_internal_net_NetworkStatsFactory.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsFactory.cpp
@@ -21,9 +21,9 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
-#include <core_jni_helpers.h>
 #include <jni.h>
 
+#include <nativehelper/JNIHelp.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <nativehelper/ScopedLocalRef.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
@@ -333,29 +333,27 @@
                 (void*) readNetworkStatsDev },
 };
 
-int register_com_android_internal_net_NetworkStatsFactory(JNIEnv* env) {
-    int err = RegisterMethodsOrDie(env,
-            "com/android/internal/net/NetworkStatsFactory", gMethods,
+int register_android_server_net_NetworkStatsFactory(JNIEnv* env) {
+    int err = jniRegisterNativeMethods(env, "com/android/server/net/NetworkStatsFactory", gMethods,
             NELEM(gMethods));
+    gStringClass = env->FindClass("java/lang/String");
+    gStringClass = static_cast<jclass>(env->NewGlobalRef(gStringClass));
 
-    gStringClass = FindClassOrDie(env, "java/lang/String");
-    gStringClass = MakeGlobalRefOrDie(env, gStringClass);
-
-    jclass clazz = FindClassOrDie(env, "android/net/NetworkStats");
-    gNetworkStatsClassInfo.size = GetFieldIDOrDie(env, clazz, "size", "I");
-    gNetworkStatsClassInfo.capacity = GetFieldIDOrDie(env, clazz, "capacity", "I");
-    gNetworkStatsClassInfo.iface = GetFieldIDOrDie(env, clazz, "iface", "[Ljava/lang/String;");
-    gNetworkStatsClassInfo.uid = GetFieldIDOrDie(env, clazz, "uid", "[I");
-    gNetworkStatsClassInfo.set = GetFieldIDOrDie(env, clazz, "set", "[I");
-    gNetworkStatsClassInfo.tag = GetFieldIDOrDie(env, clazz, "tag", "[I");
-    gNetworkStatsClassInfo.metered = GetFieldIDOrDie(env, clazz, "metered", "[I");
-    gNetworkStatsClassInfo.roaming = GetFieldIDOrDie(env, clazz, "roaming", "[I");
-    gNetworkStatsClassInfo.defaultNetwork = GetFieldIDOrDie(env, clazz, "defaultNetwork", "[I");
-    gNetworkStatsClassInfo.rxBytes = GetFieldIDOrDie(env, clazz, "rxBytes", "[J");
-    gNetworkStatsClassInfo.rxPackets = GetFieldIDOrDie(env, clazz, "rxPackets", "[J");
-    gNetworkStatsClassInfo.txBytes = GetFieldIDOrDie(env, clazz, "txBytes", "[J");
-    gNetworkStatsClassInfo.txPackets = GetFieldIDOrDie(env, clazz, "txPackets", "[J");
-    gNetworkStatsClassInfo.operations = GetFieldIDOrDie(env, clazz, "operations", "[J");
+    jclass clazz = env->FindClass("android/net/NetworkStats");
+    gNetworkStatsClassInfo.size = env->GetFieldID(clazz, "size", "I");
+    gNetworkStatsClassInfo.capacity = env->GetFieldID(clazz, "capacity", "I");
+    gNetworkStatsClassInfo.iface = env->GetFieldID(clazz, "iface", "[Ljava/lang/String;");
+    gNetworkStatsClassInfo.uid = env->GetFieldID(clazz, "uid", "[I");
+    gNetworkStatsClassInfo.set = env->GetFieldID(clazz, "set", "[I");
+    gNetworkStatsClassInfo.tag = env->GetFieldID(clazz, "tag", "[I");
+    gNetworkStatsClassInfo.metered = env->GetFieldID(clazz, "metered", "[I");
+    gNetworkStatsClassInfo.roaming = env->GetFieldID(clazz, "roaming", "[I");
+    gNetworkStatsClassInfo.defaultNetwork = env->GetFieldID(clazz, "defaultNetwork", "[I");
+    gNetworkStatsClassInfo.rxBytes = env->GetFieldID(clazz, "rxBytes", "[J");
+    gNetworkStatsClassInfo.rxPackets = env->GetFieldID(clazz, "rxPackets", "[J");
+    gNetworkStatsClassInfo.txBytes = env->GetFieldID(clazz, "txBytes", "[J");
+    gNetworkStatsClassInfo.txPackets = env->GetFieldID(clazz, "txPackets", "[J");
+    gNetworkStatsClassInfo.operations = env->GetFieldID(clazz, "operations", "[J");
 
     return err;
 }
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 2cfaebf..cce96ff 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -52,6 +52,7 @@
 int register_android_server_SyntheticPasswordManager(JNIEnv* env);
 int register_android_server_GraphicsStatsService(JNIEnv* env);
 int register_android_hardware_display_DisplayViewport(JNIEnv* env);
+int register_android_server_net_NetworkStatsFactory(JNIEnv* env);
 int register_android_server_net_NetworkStatsService(JNIEnv* env);
 int register_android_server_security_VerityUtils(JNIEnv* env);
 int register_android_server_am_AppCompactor(JNIEnv* env);
@@ -100,6 +101,7 @@
     register_android_server_SyntheticPasswordManager(env);
     register_android_server_GraphicsStatsService(env);
     register_android_hardware_display_DisplayViewport(env);
+    register_android_server_net_NetworkStatsFactory(env);
     register_android_server_net_NetworkStatsService(env);
     register_android_server_security_VerityUtils(env);
     register_android_server_am_AppCompactor(env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index b5c845a..d014c0a 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -14123,6 +14123,11 @@
     @Override
     public void installUpdateFromFile(ComponentName admin,
             ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback callback) {
+        DevicePolicyEventLogger
+                .createEvent(DevicePolicyEnums.INSTALL_SYSTEM_UPDATE)
+                .setAdmin(admin)
+                .setBoolean(isDeviceAB())
+                .write();
         enforceDeviceOwner(admin);
         final long id = mInjector.binderClearCallingIdentity();
         try {
@@ -14135,11 +14140,6 @@
                         mContext, updateFileDescriptor, callback, mInjector, mConstants);
             }
             updateInstaller.startInstallUpdate();
-            DevicePolicyEventLogger
-                    .createEvent(DevicePolicyEnums.INSTALL_SYSTEM_UPDATE)
-                    .setAdmin(admin)
-                    .setBoolean(isDeviceAB())
-                    .write();
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
         }
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 8f48f5b..f73a285 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -59,6 +59,7 @@
     srcs: ["java/**/*.java"],
     static_libs: [
         "dnsresolver_aidl_interface-java",
+        "ipmemorystore-client",
         "netd_aidl_interface-java",
         "networkstack-aidl-interfaces-java",
     ]
diff --git a/services/net/java/android/net/IIpMemoryStore.aidl b/services/net/java/android/net/IIpMemoryStore.aidl
index 6f88dec..63feae6 100644
--- a/services/net/java/android/net/IIpMemoryStore.aidl
+++ b/services/net/java/android/net/IIpMemoryStore.aidl
@@ -20,8 +20,8 @@
 import android.net.ipmemorystore.NetworkAttributesParcelable;
 import android.net.ipmemorystore.IOnBlobRetrievedListener;
 import android.net.ipmemorystore.IOnL2KeyResponseListener;
-import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
-import android.net.ipmemorystore.IOnSameNetworkResponseListener;
+import android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener;
+import android.net.ipmemorystore.IOnSameL3NetworkResponseListener;
 import android.net.ipmemorystore.IOnStatusListener;
 
 /** {@hide} */
@@ -84,7 +84,7 @@
      * @param listener The listener that will be invoked to return the answer.
      * @return (through the listener) A SameL3NetworkResponse containing the answer and confidence.
      */
-    void isSameNetwork(String l2Key1, String l2Key2, IOnSameNetworkResponseListener listener);
+    void isSameNetwork(String l2Key1, String l2Key2, IOnSameL3NetworkResponseListener listener);
 
     /**
      * Retrieve the network attributes for a key.
@@ -95,7 +95,7 @@
      * @return (through the listener) The network attributes and the L2 key associated with
      *         the query.
      */
-    void retrieveNetworkAttributes(String l2Key, IOnNetworkAttributesRetrieved listener);
+    void retrieveNetworkAttributes(String l2Key, IOnNetworkAttributesRetrievedListener listener);
 
     /**
      * Retrieve previously stored private data.
diff --git a/services/net/java/android/net/IpMemoryStoreClient.java b/services/net/java/android/net/IpMemoryStoreClient.java
index 2f4fdbd..379c017 100644
--- a/services/net/java/android/net/IpMemoryStoreClient.java
+++ b/services/net/java/android/net/IpMemoryStoreClient.java
@@ -20,14 +20,13 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.net.ipmemorystore.Blob;
-import android.net.ipmemorystore.IOnBlobRetrievedListener;
-import android.net.ipmemorystore.IOnL2KeyResponseListener;
-import android.net.ipmemorystore.IOnNetworkAttributesRetrieved;
-import android.net.ipmemorystore.IOnSameNetworkResponseListener;
-import android.net.ipmemorystore.IOnStatusListener;
 import android.net.ipmemorystore.NetworkAttributes;
+import android.net.ipmemorystore.OnBlobRetrievedListener;
+import android.net.ipmemorystore.OnL2KeyResponseListener;
+import android.net.ipmemorystore.OnNetworkAttributesRetrievedListener;
+import android.net.ipmemorystore.OnSameL3NetworkResponseListener;
+import android.net.ipmemorystore.OnStatusListener;
 import android.net.ipmemorystore.Status;
-import android.net.ipmemorystore.StatusParcelable;
 import android.os.RemoteException;
 import android.util.Log;
 
@@ -50,12 +49,6 @@
     @NonNull
     protected abstract IIpMemoryStore getService() throws InterruptedException, ExecutionException;
 
-    protected StatusParcelable internalErrorStatus() {
-        final StatusParcelable error = new StatusParcelable();
-        error.resultCode = Status.ERROR_UNKNOWN;
-        return error;
-    }
-
     /**
      * Store network attributes for a given L2 key.
      * If L2Key is null, choose automatically from the attributes ; passing null is equivalent to
@@ -74,12 +67,13 @@
      */
     public void storeNetworkAttributes(@NonNull final String l2Key,
             @NonNull final NetworkAttributes attributes,
-            @Nullable final IOnStatusListener listener) {
+            @Nullable final OnStatusListener listener) {
         try {
             try {
-                getService().storeNetworkAttributes(l2Key, attributes.toParcelable(), listener);
+                getService().storeNetworkAttributes(l2Key, attributes.toParcelable(),
+                        OnStatusListener.toAIDL(listener));
             } catch (InterruptedException | ExecutionException m) {
-                listener.onComplete(internalErrorStatus());
+                listener.onComplete(new Status(Status.ERROR_UNKNOWN));
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error storing network attributes", e);
@@ -99,12 +93,13 @@
      */
     public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId,
             @NonNull final String name, @NonNull final Blob data,
-            @Nullable final IOnStatusListener listener) {
+            @Nullable final OnStatusListener listener) {
         try {
             try {
-                getService().storeBlob(l2Key, clientId, name, data, listener);
+                getService().storeBlob(l2Key, clientId, name, data,
+                        OnStatusListener.toAIDL(listener));
             } catch (InterruptedException | ExecutionException m) {
-                listener.onComplete(internalErrorStatus());
+                listener.onComplete(new Status(Status.ERROR_UNKNOWN));
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error storing blob", e);
@@ -126,12 +121,13 @@
      * Through the listener, returns the L2 key if one matched, or null.
      */
     public void findL2Key(@NonNull final NetworkAttributes attributes,
-            @NonNull final IOnL2KeyResponseListener listener) {
+            @NonNull final OnL2KeyResponseListener listener) {
         try {
             try {
-                getService().findL2Key(attributes.toParcelable(), listener);
+                getService().findL2Key(attributes.toParcelable(),
+                        OnL2KeyResponseListener.toAIDL(listener));
             } catch (InterruptedException | ExecutionException m) {
-                listener.onL2KeyResponse(internalErrorStatus(), null);
+                listener.onL2KeyResponse(new Status(Status.ERROR_UNKNOWN), null);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error finding L2 Key", e);
@@ -148,12 +144,13 @@
      * Through the listener, a SameL3NetworkResponse containing the answer and confidence.
      */
     public void isSameNetwork(@NonNull final String l2Key1, @NonNull final String l2Key2,
-            @NonNull final IOnSameNetworkResponseListener listener) {
+            @NonNull final OnSameL3NetworkResponseListener listener) {
         try {
             try {
-                getService().isSameNetwork(l2Key1, l2Key2, listener);
+                getService().isSameNetwork(l2Key1, l2Key2,
+                        OnSameL3NetworkResponseListener.toAIDL(listener));
             } catch (InterruptedException | ExecutionException m) {
-                listener.onSameNetworkResponse(internalErrorStatus(), null);
+                listener.onSameL3NetworkResponse(new Status(Status.ERROR_UNKNOWN), null);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error checking for network sameness", e);
@@ -170,12 +167,13 @@
      *         the query.
      */
     public void retrieveNetworkAttributes(@NonNull final String l2Key,
-            @NonNull final IOnNetworkAttributesRetrieved listener) {
+            @NonNull final OnNetworkAttributesRetrievedListener listener) {
         try {
             try {
-                getService().retrieveNetworkAttributes(l2Key, listener);
+                getService().retrieveNetworkAttributes(l2Key,
+                        OnNetworkAttributesRetrievedListener.toAIDL(listener));
             } catch (InterruptedException | ExecutionException m) {
-                listener.onNetworkAttributesRetrieved(internalErrorStatus(), null, null);
+                listener.onNetworkAttributesRetrieved(new Status(Status.ERROR_UNKNOWN), null, null);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error retrieving network attributes", e);
@@ -194,12 +192,13 @@
      *         and the name of the data associated with the query.
      */
     public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId,
-            @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) {
+            @NonNull final String name, @NonNull final OnBlobRetrievedListener listener) {
         try {
             try {
-                getService().retrieveBlob(l2Key, clientId, name, listener);
+                getService().retrieveBlob(l2Key, clientId, name,
+                        OnBlobRetrievedListener.toAIDL(listener));
             } catch (InterruptedException | ExecutionException m) {
-                listener.onBlobRetrieved(internalErrorStatus(), null, null, null);
+                listener.onBlobRetrieved(new Status(Status.ERROR_UNKNOWN), null, null, null);
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error retrieving blob", e);
diff --git a/services/net/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl b/services/net/java/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
similarity index 94%
rename from services/net/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
rename to services/net/java/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
index fb4ca3b..870e217 100644
--- a/services/net/java/android/net/ipmemorystore/IOnNetworkAttributesRetrieved.aidl
+++ b/services/net/java/android/net/ipmemorystore/IOnNetworkAttributesRetrievedListener.aidl
@@ -20,7 +20,7 @@
 import android.net.ipmemorystore.StatusParcelable;
 
 /** {@hide} */
-oneway interface IOnNetworkAttributesRetrieved {
+oneway interface IOnNetworkAttributesRetrievedListener {
     /**
      * Network attributes were fetched for the specified L2 key. While the L2 key will never
      * be null, the attributes may be if no data is stored about this L2 key.
diff --git a/services/net/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl b/services/net/java/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
similarity index 89%
rename from services/net/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl
rename to services/net/java/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
index 294bd3b..b8ccfb9 100644
--- a/services/net/java/android/net/ipmemorystore/IOnSameNetworkResponseListener.aidl
+++ b/services/net/java/android/net/ipmemorystore/IOnSameL3NetworkResponseListener.aidl
@@ -20,10 +20,10 @@
 import android.net.ipmemorystore.StatusParcelable;
 
 /** {@hide} */
-oneway interface IOnSameNetworkResponseListener {
+oneway interface IOnSameL3NetworkResponseListener {
     /**
      * The memory store has come up with the answer to a query that was sent.
      */
-     void onSameNetworkResponse(in StatusParcelable status,
+     void onSameL3NetworkResponse(in StatusParcelable status,
              in SameL3NetworkResponseParcelable response);
 }
diff --git a/services/net/java/android/net/ipmemorystore/OnBlobRetrievedListener.java b/services/net/java/android/net/ipmemorystore/OnBlobRetrievedListener.java
new file mode 100644
index 0000000..9685ff6
--- /dev/null
+++ b/services/net/java/android/net/ipmemorystore/OnBlobRetrievedListener.java
@@ -0,0 +1,42 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A listener for the IpMemoryStore to return a blob.
+ * @hide
+ */
+public interface OnBlobRetrievedListener {
+    /**
+     * The memory store has come up with the answer to a query that was sent.
+     */
+    void onBlobRetrieved(Status status, String l2Key, String name, Blob blob);
+
+    /** Converts this OnBlobRetrievedListener to a parcelable object */
+    @NonNull
+    static IOnBlobRetrievedListener toAIDL(final OnBlobRetrievedListener listener) {
+        return new IOnBlobRetrievedListener.Stub() {
+            @Override
+            public void onBlobRetrieved(final StatusParcelable statusParcelable, final String l2Key,
+                    final String name, final Blob blob) {
+                listener.onBlobRetrieved(new Status(statusParcelable), l2Key, name, blob);
+            }
+        };
+    }
+}
diff --git a/services/net/java/android/net/ipmemorystore/OnL2KeyResponseListener.java b/services/net/java/android/net/ipmemorystore/OnL2KeyResponseListener.java
new file mode 100644
index 0000000..80209c5
--- /dev/null
+++ b/services/net/java/android/net/ipmemorystore/OnL2KeyResponseListener.java
@@ -0,0 +1,42 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A listener for the IpMemoryStore to return a L2 key.
+ * @hide
+ */
+public interface OnL2KeyResponseListener {
+    /**
+     * The operation has completed with the specified status.
+     */
+    void onL2KeyResponse(Status status, String l2Key);
+
+    /** Converts this OnL2KeyResponseListener to a parcelable object */
+    @NonNull
+    static IOnL2KeyResponseListener toAIDL(final OnL2KeyResponseListener listener) {
+        return new IOnL2KeyResponseListener.Stub() {
+            @Override
+            public void onL2KeyResponse(final StatusParcelable statusParcelable,
+                    final String l2Key) {
+                listener.onL2KeyResponse(new Status(statusParcelable), l2Key);
+            }
+        };
+    }
+}
diff --git a/services/net/java/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java b/services/net/java/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java
new file mode 100644
index 0000000..f0f6f40
--- /dev/null
+++ b/services/net/java/android/net/ipmemorystore/OnNetworkAttributesRetrievedListener.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A listener for the IpMemoryStore to return network attributes.
+ * @hide
+ */
+public interface OnNetworkAttributesRetrievedListener {
+    /**
+     * The memory store has come up with the answer to a query that was sent.
+     */
+    void onNetworkAttributesRetrieved(Status status, String l2Key, NetworkAttributes attributes);
+
+    /** Converts this OnNetworkAttributesRetrievedListener to a parcelable object */
+    @NonNull
+    static IOnNetworkAttributesRetrievedListener toAIDL(
+            final OnNetworkAttributesRetrievedListener listener) {
+        return new IOnNetworkAttributesRetrievedListener.Stub() {
+            @Override
+            public void onNetworkAttributesRetrieved(final StatusParcelable statusParcelable,
+                    final String l2Key,
+                    final NetworkAttributesParcelable networkAttributesParcelable) {
+                listener.onNetworkAttributesRetrieved(
+                        new Status(statusParcelable), l2Key,
+                        new NetworkAttributes(networkAttributesParcelable));
+            }
+        };
+    }
+}
diff --git a/services/net/java/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java b/services/net/java/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java
new file mode 100644
index 0000000..ba1e0e6
--- /dev/null
+++ b/services/net/java/android/net/ipmemorystore/OnSameL3NetworkResponseListener.java
@@ -0,0 +1,44 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A listener for the IpMemoryStore to return a response about network sameness.
+ * @hide
+ */
+public interface OnSameL3NetworkResponseListener {
+    /**
+     * The memory store has come up with the answer to a query that was sent.
+     */
+    void onSameL3NetworkResponse(Status status, SameL3NetworkResponse response);
+
+    /** Converts this OnSameL3NetworkResponseListener to a parcelable object */
+    @NonNull
+    static IOnSameL3NetworkResponseListener toAIDL(final OnSameL3NetworkResponseListener listener) {
+        return new IOnSameL3NetworkResponseListener.Stub() {
+            @Override
+            public void onSameL3NetworkResponse(final StatusParcelable statusParcelable,
+                    final SameL3NetworkResponseParcelable sameL3NetworkResponseParcelable) {
+                listener.onSameL3NetworkResponse(
+                        new Status(statusParcelable),
+                        new SameL3NetworkResponse(sameL3NetworkResponseParcelable));
+            }
+        };
+    }
+}
diff --git a/services/net/java/android/net/ipmemorystore/OnStatusListener.java b/services/net/java/android/net/ipmemorystore/OnStatusListener.java
new file mode 100644
index 0000000..0de1666
--- /dev/null
+++ b/services/net/java/android/net/ipmemorystore/OnStatusListener.java
@@ -0,0 +1,41 @@
+/*
+ * 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.net.ipmemorystore;
+
+import android.annotation.NonNull;
+
+/**
+ * A listener for the IpMemoryStore to return a status to a client.
+ * @hide
+ */
+public interface OnStatusListener {
+    /**
+     * The operation has completed with the specified status.
+     */
+    void onComplete(Status status);
+
+    /** Converts this OnStatusListener to a parcelable object */
+    @NonNull
+    static IOnStatusListener toAIDL(final OnStatusListener listener) {
+        return new IOnStatusListener.Stub() {
+            @Override
+            public void onComplete(final StatusParcelable statusParcelable) {
+                listener.onComplete(new Status(statusParcelable));
+            }
+        };
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java
index b0c97d1..475901a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java
@@ -25,6 +25,7 @@
 import android.os.HandlerThread;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
+import android.text.TextUtils;
 
 import com.android.server.appop.AppOpsService;
 import com.android.server.testables.TestableDeviceConfig;
@@ -38,6 +39,8 @@
 import org.mockito.junit.MockitoJUnitRunner;
 
 import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -103,6 +106,22 @@
                 AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
         assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
                 AppCompactor.DEFAULT_STATSD_SAMPLE_RATE);
+        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
+        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+        assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+
+        Set<Integer> expected = new HashSet<>();
+        for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
+            expected.add(Integer.parseInt(s));
+        }
+        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
     }
 
     @Test
@@ -139,6 +158,14 @@
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                 AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
                 Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
+                Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
+                Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
 
         // Then calling init will read and set that flag.
         mCompactorUnderTest.init();
@@ -157,12 +184,19 @@
                 AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1);
         assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
                 AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1);
+        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
+        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
         assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
                 AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
         assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
                 AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
         assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
                 AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
+        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
+        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
     }
 
     @Test
@@ -321,6 +355,10 @@
                 AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
                 AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
 
         // Repeat for each of the throttle keys.
         mCountDown = new CountDownLatch(1);
@@ -335,6 +373,10 @@
                 AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
                 AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
 
         mCountDown = new CountDownLatch(1);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -348,6 +390,10 @@
                 AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
                 AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
 
         mCountDown = new CountDownLatch(1);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -361,13 +407,51 @@
                 AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
         assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
                 AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_THROTTLE_5, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_THROTTLE_6, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
+        assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
+        assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
+        assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
+        assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
+        assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
     }
 
     @Test
     public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException {
         mCompactorUnderTest.init();
 
-        // When we override mStatsdSampleRate with a reasonable values ...
+        // When we override mStatsdSampleRate with a reasonable value ...
         mCountDown = new CountDownLatch(1);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                 AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
@@ -380,11 +464,11 @@
     }
 
     @Test
-    public void statsdSanokeRate_listensToDeviceConfigChangesBadValues()
+    public void statsdSampleRate_listensToDeviceConfigChangesBadValues()
             throws InterruptedException {
         mCompactorUnderTest.init();
 
-        // When we override mStatsdSampleRate with a reasonable values ...
+        // When we override mStatsdSampleRate with an unreasonable value ...
         mCountDown = new CountDownLatch(1);
         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                 AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false);
@@ -396,7 +480,7 @@
     }
 
     @Test
-    public void statsdSanokeRate_listensToDeviceConfigChangesOutOfRangeValues()
+    public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues()
             throws InterruptedException {
         mCompactorUnderTest.init();
 
@@ -420,6 +504,147 @@
         assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(1.0f);
     }
 
+    @Test
+    public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges()
+            throws InterruptedException {
+        mCompactorUnderTest.init();
+
+        // When we override mStatsdSampleRate with a reasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
+                Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
+    }
+
+    @Test
+    public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues()
+            throws InterruptedException {
+        mCompactorUnderTest.init();
+
+        // When we override mStatsdSampleRate with an unreasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+    }
+
+    @Test
+    public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges()
+            throws InterruptedException {
+        mCompactorUnderTest.init();
+
+        // When we override mStatsdSampleRate with a reasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
+                Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1);
+    }
+
+    @Test
+    public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues()
+            throws InterruptedException {
+        mCompactorUnderTest.init();
+
+        // When we override mStatsdSampleRate with an unreasonable value ...
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+        // Then that override is reflected in the compactor.
+        assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+                AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+    }
+
+    @Test
+    public void procStateThrottle_listensToDeviceConfigChanges()
+            throws InterruptedException {
+        mCompactorUnderTest.init();
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
+
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCompactorUnderTest.mProcStateThrottle).isEmpty();
+    }
+
+    @Test
+    public void procStateThrottle_listensToDeviceConfigChangesBadValues()
+            throws InterruptedException {
+        mCompactorUnderTest.init();
+
+        Set<Integer> expected = new HashSet<>();
+        for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
+            expected.add(Integer.parseInt(s));
+        }
+
+        // Not numbers
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
+
+        // Empty splits
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
+        mCountDown = new CountDownLatch(1);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false);
+        assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+        assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
+    }
+
     private class TestInjector extends Injector {
         @Override
         public AppOpsService getAppOpsService(File file, Handler handler) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index 2ed25ea..f336497 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -23,10 +23,12 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -46,6 +48,7 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 
 
@@ -149,4 +152,21 @@
         assertTrue(mConnection.getServiceInfo().crashed);
         verify(mMockKeyEventDispatcher).flush(mConnection);
     }
+
+    @Test
+    public void connectedService_notInEnabledServiceList_doNotInitClient()
+            throws RemoteException {
+        IBinder mockBinder = mock(IBinder.class);
+        IAccessibilityServiceClient mockClient = mock(IAccessibilityServiceClient.class);
+        when(mockBinder.queryLocalInterface(any())).thenReturn(mockClient);
+        when(mMockUserState.getEnabledServicesLocked())
+                .thenReturn(Collections.emptySet());
+        setServiceBinding(COMPONENT_NAME);
+
+        mConnection.bindLocked();
+        mConnection.onServiceConnected(COMPONENT_NAME, mockBinder);
+        mHandler.sendAllMessages();
+        verify(mMockSystemSupport, times(2)).onClientChangeLocked(false);
+        verify(mockClient, times(0)).init(any(), anyInt(), any());
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index f39c716..46d0761 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -68,12 +68,15 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
+import android.os.storage.IStorageManager;
 import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
+
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.FgThread;
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.WindowManagerService;
 
@@ -82,7 +85,9 @@
 import org.junit.Test;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
@@ -95,12 +100,20 @@
  */
 @SmallTest
 @Presubmit
+
 public class UserControllerTest {
-    private static final int TEST_USER_ID = 10;
+    // Use big enough user id to avoid picking up already active user id.
+    private static final int TEST_USER_ID = 100;
+    private static final int TEST_USER_ID1 = 101;
+    private static final int TEST_USER_ID2 = 102;
     private static final int NONEXIST_USER_ID = 2;
     private static final String TAG = UserControllerTest.class.getSimpleName();
+
+    private static final long HANDLER_WAIT_TIME_MS = 100;
+
     private UserController mUserController;
     private TestInjector mInjector;
+    private final HashMap<Integer, UserState> mUserStates = new HashMap<>();
 
     private static final List<String> START_FOREGROUND_USER_ACTIONS = newArrayList(
             Intent.ACTION_USER_STARTED,
@@ -130,6 +143,11 @@
             doNothing().when(mInjector).startHomeActivity(anyInt(), anyString());
             doReturn(false).when(mInjector).stackSupervisorSwitchUser(anyInt(), any());
             doNothing().when(mInjector).stackSupervisorResumeFocusedStackTopActivity();
+            doNothing().when(mInjector).systemServiceManagerCleanupUser(anyInt());
+            doNothing().when(mInjector).activityManagerForceStopPackage(anyInt(), anyString());
+            doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
+            doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
+            doNothing().when(mInjector).stackSupervisorRemoveUser(anyInt());
             mUserController = new UserController(mInjector);
             setUpUser(TEST_USER_ID, 0);
         });
@@ -261,7 +279,7 @@
     }
 
     @Test
-    public void testContinueUserSwitch() {
+    public void testContinueUserSwitch() throws RemoteException {
         // Start user -- this will update state of mUserController
         mUserController.startUser(TEST_USER_ID, true);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -273,11 +291,11 @@
         // Verify that continueUserSwitch worked as expected
         mUserController.continueUserSwitch(userState, oldUserId, newUserId);
         verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
-        continueUserSwitchAssertions();
+        continueUserSwitchAssertions(TEST_USER_ID, false);
     }
 
     @Test
-    public void testContinueUserSwitchUIDisabled() {
+    public void testContinueUserSwitchUIDisabled() throws RemoteException {
         mUserController.mUserSwitchUiEnabled = false;
         // Start user -- this will update state of mUserController
         mUserController.startUser(TEST_USER_ID, true);
@@ -290,16 +308,21 @@
         // Verify that continueUserSwitch worked as expected
         mUserController.continueUserSwitch(userState, oldUserId, newUserId);
         verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
-        continueUserSwitchAssertions();
+        continueUserSwitchAssertions(TEST_USER_ID, false);
     }
 
-    private void continueUserSwitchAssertions() {
-        Set<Integer> expectedCodes = Collections.singleton(REPORT_USER_SWITCH_COMPLETE_MSG);
+    private void continueUserSwitchAssertions(int expectedUserId, boolean backgroundUserStopping)
+            throws RemoteException {
+        Set<Integer> expectedCodes = new LinkedHashSet<>();
+        expectedCodes.add(REPORT_USER_SWITCH_COMPLETE_MSG);
+        if (backgroundUserStopping) {
+            expectedCodes.add(0); // this is for directly posting in stopping.
+        }
         Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes();
         assertEquals("Unexpected message sent", expectedCodes, actualCodes);
         Message msg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_COMPLETE_MSG);
         assertNotNull(msg);
-        assertEquals("Unexpected userId", TEST_USER_ID, msg.arg1);
+        assertEquals("Unexpected userId", expectedUserId, msg.arg1);
     }
 
     @Test
@@ -332,6 +355,120 @@
         assertTrue(mUserController.isSystemUserStarted());
     }
 
+    /**
+     * Test stopping of user from max running users limit.
+     */
+    @Test
+    public void testUserStoppingForMultipleUsersNormalMode()
+            throws InterruptedException, RemoteException {
+        setUpUser(TEST_USER_ID1, 0);
+        setUpUser(TEST_USER_ID2, 0);
+        mUserController.mMaxRunningUsers = 3;
+        int numerOfUserSwitches = 1;
+        addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
+                numerOfUserSwitches, false);
+        // running: user 0, USER_ID
+        assertTrue(mUserController.canStartMoreUsers());
+        assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}),
+                mUserController.getRunningUsersLU());
+
+        numerOfUserSwitches++;
+        addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
+                numerOfUserSwitches, false);
+        // running: user 0, USER_ID, USER_ID1
+        assertFalse(mUserController.canStartMoreUsers());
+        assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID, TEST_USER_ID1}),
+                mUserController.getRunningUsersLU());
+
+        numerOfUserSwitches++;
+        addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
+                numerOfUserSwitches, false);
+        UserState ussUser2 = mUserStates.get(TEST_USER_ID2);
+        // skip middle step and call this directly.
+        mUserController.finishUserSwitch(ussUser2);
+        waitForHandlerToComplete(mInjector.mHandler, HANDLER_WAIT_TIME_MS);
+        // running: user 0, USER_ID1, USER_ID2
+        // USER_ID should be stopped as it is least recently used non user0.
+        assertFalse(mUserController.canStartMoreUsers());
+        assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}),
+                mUserController.getRunningUsersLU());
+    }
+
+    /**
+     * This test tests delayed locking mode using 4 users. As core logic of delayed locking is
+     * happening in finishUserStopped call, the test also calls finishUserStopped while skipping
+     * all middle steps which takes too much work to mock.
+     */
+    @Test
+    public void testUserStoppingForMultipleUsersDelayedLockingMode()
+            throws InterruptedException, RemoteException {
+        setUpUser(TEST_USER_ID1, 0);
+        setUpUser(TEST_USER_ID2, 0);
+        mUserController.mMaxRunningUsers = 3;
+        mUserController.mDelayUserDataLocking = true;
+        int numerOfUserSwitches = 1;
+        addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
+                numerOfUserSwitches, false);
+        // running: user 0, USER_ID
+        assertTrue(mUserController.canStartMoreUsers());
+        assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID}),
+                mUserController.getRunningUsersLU());
+        numerOfUserSwitches++;
+
+        addForegroundUserAndContinueUserSwitch(TEST_USER_ID1, TEST_USER_ID,
+                numerOfUserSwitches, true);
+        // running: user 0, USER_ID1
+        // stopped + unlocked: USER_ID
+        numerOfUserSwitches++;
+        assertTrue(mUserController.canStartMoreUsers());
+        assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1}),
+                mUserController.getRunningUsersLU());
+        // Skip all other steps and test unlock delaying only
+        UserState uss = mUserStates.get(TEST_USER_ID);
+        uss.setState(UserState.STATE_SHUTDOWN); // necessary state change from skipped part
+        mUserController.finishUserStopped(uss);
+        // Cannot mock FgThread handler, so confirm that there is no posted message left before
+        // checking.
+        waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
+        verify(mInjector.mStorageManagerMock, times(0))
+                .lockUserKey(anyInt());
+
+        addForegroundUserAndContinueUserSwitch(TEST_USER_ID2, TEST_USER_ID1,
+                numerOfUserSwitches, true);
+        // running: user 0, USER_ID2
+        // stopped + unlocked: USER_ID1
+        // stopped + locked: USER_ID
+        assertTrue(mUserController.canStartMoreUsers());
+        assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID2}),
+                mUserController.getRunningUsersLU());
+        UserState ussUser1 = mUserStates.get(TEST_USER_ID1);
+        ussUser1.setState(UserState.STATE_SHUTDOWN);
+        mUserController.finishUserStopped(ussUser1);
+        waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
+        verify(mInjector.mStorageManagerMock, times(1))
+                .lockUserKey(TEST_USER_ID);
+    }
+
+    private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
+            int expectedNumberOfCalls, boolean expectOldUserStopping)
+            throws RemoteException {
+        // Start user -- this will update state of mUserController
+        mUserController.startUser(newUserId, true);
+        Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
+        assertNotNull(reportMsg);
+        UserState userState = (UserState) reportMsg.obj;
+        int oldUserId = reportMsg.arg1;
+        assertEquals(expectedOldUserId, oldUserId);
+        assertEquals(newUserId, reportMsg.arg2);
+        mUserStates.put(newUserId, userState);
+        mInjector.mHandler.clearAllRecordedMessages();
+        // Verify that continueUserSwitch worked as expected
+        mUserController.continueUserSwitch(userState, oldUserId, newUserId);
+        verify(mInjector.getWindowManager(), times(expectedNumberOfCalls))
+                .stopFreezingScreen();
+        continueUserSwitchAssertions(newUserId, expectOldUserStopping);
+    }
+
     private void setUpUser(int userId, int flags) {
         UserInfo userInfo = new UserInfo(userId, "User" + userId, flags);
         when(mInjector.mUserManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo);
@@ -345,6 +482,22 @@
         return result;
     }
 
+    private void waitForHandlerToComplete(Handler handler, long waitTimeMs)
+            throws InterruptedException {
+        if (!handler.hasMessagesOrCallbacks()) { // if nothing queued, do not wait.
+            return;
+        }
+        final Object lock = new Object();
+        synchronized (lock) {
+            handler.post(() -> {
+                synchronized (lock) {
+                    lock.notify();
+                }
+            });
+            lock.wait(waitTimeMs);
+        }
+    }
+
     // Should be public to allow mocking
     private static class TestInjector extends UserController.Injector {
         public final TestHandler mHandler;
@@ -353,8 +506,11 @@
         public final List<Intent> mSentIntents = new ArrayList<>();
 
         private final TestHandler mUiHandler;
+
+        private final IStorageManager mStorageManagerMock;
         private final UserManagerInternal mUserManagerInternalMock;
         private final WindowManagerService mWindowManagerMock;
+
         private final Context mCtx;
 
         TestInjector(Context ctx) {
@@ -367,6 +523,7 @@
             mUserManagerMock = mock(UserManagerService.class);
             mUserManagerInternalMock = mock(UserManagerInternal.class);
             mWindowManagerMock = mock(WindowManagerService.class);
+            mStorageManagerMock = mock(IStorageManager.class);
         }
 
         @Override
@@ -434,6 +591,11 @@
             // to pass all metrics related calls
             return true;
         }
+
+        @Override
+        protected IStorageManager getStorageManager() {
+            return mStorageManagerMock;
+        }
     }
 
     private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 2fbeebd..09e20e0 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -46,6 +46,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.LocalServices;
+import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
 import com.android.server.wm.WindowManagerInternal;
 
 import org.mockito.invocation.InvocationOnMock;
@@ -89,6 +90,7 @@
     WindowManagerInternal mMockWindowManager;
     FakeGsiService mGsiService;
     PasswordSlotManagerTestable mPasswordSlotManager;
+    RecoverableKeyStoreManager mRecoverableKeyStoreManager;
     protected boolean mHasSecureLockScreen;
 
     @Override
@@ -105,6 +107,7 @@
         mMockWindowManager = mock(WindowManagerInternal.class);
         mGsiService = new FakeGsiService();
         mPasswordSlotManager = new PasswordSlotManagerTestable();
+        mRecoverableKeyStoreManager = mock(RecoverableKeyStoreManager.class);
 
         LocalServices.removeServiceForTest(LockSettingsInternal.class);
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
@@ -141,12 +144,14 @@
         mAuthSecretService = mock(IAuthSecret.class);
         mService = new LockSettingsServiceTestable(mContext, mLockPatternUtils, mStorage,
                 mGateKeeperService, mKeyStore, setUpStorageManagerMock(), mActivityManager,
-                mSpManager, mAuthSecretService, mGsiService);
+                mSpManager, mAuthSecretService, mGsiService, mRecoverableKeyStoreManager);
         when(mUserManager.getUserInfo(eq(PRIMARY_USER_ID))).thenReturn(PRIMARY_USER_INFO);
         mPrimaryUserProfiles.add(PRIMARY_USER_INFO);
         installChildProfile(MANAGED_PROFILE_USER_ID);
         installAndTurnOffChildProfile(TURNED_OFF_PROFILE_USER_ID);
-        when(mUserManager.getProfiles(eq(PRIMARY_USER_ID))).thenReturn(mPrimaryUserProfiles);
+        for (UserInfo profile : mPrimaryUserProfiles) {
+            when(mUserManager.getProfiles(eq(profile.id))).thenReturn(mPrimaryUserProfiles);
+        }
         when(mUserManager.getUserInfo(eq(SECONDARY_USER_ID))).thenReturn(SECONDARY_USER_INFO);
 
         final ArrayList<UserInfo> allUsers = new ArrayList<>(mPrimaryUserProfiles);
@@ -173,6 +178,7 @@
     private UserInfo installChildProfile(int profileId) {
         final UserInfo userInfo = new UserInfo(
             profileId, null, null, UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_MANAGED_PROFILE);
+        userInfo.profileGroupId = PRIMARY_USER_ID;
         mPrimaryUserProfiles.add(userInfo);
         when(mUserManager.getUserInfo(eq(profileId))).thenReturn(userInfo);
         when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index f4632db..10fb3ba 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -30,6 +30,7 @@
 import android.security.keystore.KeyPermanentlyInvalidatedException;
 
 import com.android.internal.widget.LockPatternUtils;
+import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
 
 import java.io.FileNotFoundException;
 
@@ -45,11 +46,13 @@
         private SyntheticPasswordManager mSpManager;
         private IAuthSecret mAuthSecretService;
         private FakeGsiService mGsiService;
+        private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
 
         public MockInjector(Context context, LockSettingsStorage storage, KeyStore keyStore,
                 IActivityManager activityManager, LockPatternUtils lockPatternUtils,
                 IStorageManager storageManager, SyntheticPasswordManager spManager,
-                IAuthSecret authSecretService, FakeGsiService gsiService) {
+                IAuthSecret authSecretService, FakeGsiService gsiService,
+                RecoverableKeyStoreManager recoverableKeyStoreManager) {
             super(context);
             mLockSettingsStorage = storage;
             mKeyStore = keyStore;
@@ -58,6 +61,7 @@
             mStorageManager = storageManager;
             mSpManager = spManager;
             mGsiService = gsiService;
+            mRecoverableKeyStoreManager = recoverableKeyStoreManager;
         }
 
         @Override
@@ -119,15 +123,21 @@
         public boolean isGsiRunning() {
             return mGsiService.isGsiRunning();
         }
+
+        @Override
+        public RecoverableKeyStoreManager getRecoverableKeyStoreManager(KeyStore keyStore) {
+            return mRecoverableKeyStoreManager;
+        }
     }
 
     protected LockSettingsServiceTestable(Context context, LockPatternUtils lockPatternUtils,
             LockSettingsStorage storage, FakeGateKeeperService gatekeeper, KeyStore keystore,
             IStorageManager storageManager, IActivityManager mActivityManager,
             SyntheticPasswordManager spManager, IAuthSecret authSecretService,
-            FakeGsiService gsiService) {
+            FakeGsiService gsiService, RecoverableKeyStoreManager recoverableKeyStoreManager) {
         super(new MockInjector(context, storage, keystore, mActivityManager, lockPatternUtils,
-                storageManager, spManager, authSecretService, gsiService));
+                storageManager, spManager, authSecretService, gsiService,
+                recoverableKeyStoreManager));
         mGateKeeperService = gatekeeper;
         mAuthSecretService = authSecretService;
     }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 7ebc745..67d6eda 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -25,6 +25,13 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.service.gatekeeper.GateKeeperResponse;
@@ -211,6 +218,222 @@
         assertEquals(profileSid, mGateKeeperService.getSecureUserId(MANAGED_PROFILE_USER_ID));
     }
 
+    public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception {
+        final byte[] password = "password".getBytes();
+
+        mService.setLockCredential(
+                password,
+                CREDENTIAL_TYPE_PASSWORD,
+                null,
+                PASSWORD_QUALITY_ALPHABETIC,
+                PRIMARY_USER_ID,
+                false);
+
+        verify(mRecoverableKeyStoreManager)
+                .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, password, PRIMARY_USER_ID);
+    }
+
+    public void testSetLockCredential_forProfileWithSeparateChallenge_sendsCredentials()
+            throws Exception {
+        final byte[] pattern = "12345".getBytes();
+
+        mService.setLockCredential(
+                pattern,
+                CREDENTIAL_TYPE_PATTERN,
+                null,
+                PASSWORD_QUALITY_SOMETHING,
+                MANAGED_PROFILE_USER_ID,
+                false);
+
+        verify(mRecoverableKeyStoreManager)
+                .lockScreenSecretChanged(CREDENTIAL_TYPE_PATTERN, pattern, MANAGED_PROFILE_USER_ID);
+    }
+
+    public void testSetLockCredential_forProfileWithSeparateChallenge_updatesCredentials()
+            throws Exception {
+        final String oldCredential = "12345";
+        final byte[] newCredential = "newPassword".getBytes();
+        initializeStorageWithCredential(
+                MANAGED_PROFILE_USER_ID,
+                oldCredential,
+                CREDENTIAL_TYPE_PATTERN,
+                PASSWORD_QUALITY_SOMETHING);
+
+        mService.setLockCredential(
+                newCredential,
+                CREDENTIAL_TYPE_PASSWORD,
+                oldCredential.getBytes(),
+                PASSWORD_QUALITY_ALPHABETIC,
+                MANAGED_PROFILE_USER_ID,
+                false);
+
+        verify(mRecoverableKeyStoreManager)
+                .lockScreenSecretChanged(
+                        CREDENTIAL_TYPE_PASSWORD, newCredential, MANAGED_PROFILE_USER_ID);
+    }
+
+    public void testSetLockCredential_forProfileWithUnifiedChallenge_doesNotSendRandomCredential()
+            throws Exception {
+        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+
+        mService.setLockCredential(
+                "12345".getBytes(),
+                CREDENTIAL_TYPE_PATTERN,
+                null,
+                PASSWORD_QUALITY_SOMETHING,
+                PRIMARY_USER_ID,
+                false);
+
+        verify(mRecoverableKeyStoreManager, never())
+                .lockScreenSecretChanged(
+                        eq(CREDENTIAL_TYPE_PASSWORD), any(), eq(MANAGED_PROFILE_USER_ID));
+    }
+
+    public void
+            testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_updatesBothCredentials()
+                    throws Exception {
+        final String oldCredential = "oldPassword";
+        final byte[] newCredential = "newPassword".getBytes();
+        initializeStorageWithCredential(
+                PRIMARY_USER_ID, oldCredential, CREDENTIAL_TYPE_PASSWORD, 1234);
+        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+
+        mService.setLockCredential(
+                newCredential,
+                CREDENTIAL_TYPE_PASSWORD,
+                oldCredential.getBytes(),
+                PASSWORD_QUALITY_ALPHABETIC,
+                PRIMARY_USER_ID,
+                false);
+
+        verify(mRecoverableKeyStoreManager)
+                .lockScreenSecretChanged(CREDENTIAL_TYPE_PASSWORD, newCredential, PRIMARY_USER_ID);
+        verify(mRecoverableKeyStoreManager)
+                .lockScreenSecretChanged(
+                        CREDENTIAL_TYPE_PASSWORD, newCredential, MANAGED_PROFILE_USER_ID);
+    }
+
+    public void
+            testSetLockCredential_forPrimaryUserWithUnifiedChallengeProfile_removesBothCredentials()
+                    throws Exception {
+        final String oldCredential = "oldPassword";
+        initializeStorageWithCredential(
+                PRIMARY_USER_ID, oldCredential, CREDENTIAL_TYPE_PASSWORD, 1234);
+        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+
+        mService.setLockCredential(
+                null,
+                CREDENTIAL_TYPE_NONE,
+                oldCredential.getBytes(),
+                PASSWORD_QUALITY_UNSPECIFIED,
+                PRIMARY_USER_ID,
+                false);
+
+        verify(mRecoverableKeyStoreManager)
+                .lockScreenSecretChanged(CREDENTIAL_TYPE_NONE, null, PRIMARY_USER_ID);
+        verify(mRecoverableKeyStoreManager)
+                .lockScreenSecretChanged(CREDENTIAL_TYPE_NONE, null, MANAGED_PROFILE_USER_ID);
+    }
+
+    public void testSetLockCredential_forUnifiedToSeparateChallengeProfile_sendsNewCredentials()
+            throws Exception {
+        final String parentPassword = "parentPassword";
+        final byte[] profilePassword = "profilePassword".getBytes();
+        initializeStorageWithCredential(
+                PRIMARY_USER_ID, parentPassword, CREDENTIAL_TYPE_PASSWORD, 1234);
+        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+
+        mService.setLockCredential(
+                profilePassword,
+                CREDENTIAL_TYPE_PASSWORD,
+                null,
+                PASSWORD_QUALITY_ALPHABETIC,
+                MANAGED_PROFILE_USER_ID,
+                false);
+
+        verify(mRecoverableKeyStoreManager)
+                .lockScreenSecretChanged(
+                        CREDENTIAL_TYPE_PASSWORD, profilePassword, MANAGED_PROFILE_USER_ID);
+    }
+
+    public void
+            testSetLockCredential_forSeparateToUnifiedChallengeProfile_doesNotSendRandomCredential()
+                    throws Exception {
+        final String parentPassword = "parentPassword";
+        final String profilePassword = "12345";
+        initializeStorageWithCredential(
+                PRIMARY_USER_ID, parentPassword, CREDENTIAL_TYPE_PASSWORD, 1234);
+        // Create and verify separate profile credentials.
+        testCreateCredential(
+                MANAGED_PROFILE_USER_ID,
+                profilePassword,
+                CREDENTIAL_TYPE_PATTERN,
+                PASSWORD_QUALITY_SOMETHING);
+
+        mService.setSeparateProfileChallengeEnabled(
+                MANAGED_PROFILE_USER_ID, false, profilePassword.getBytes());
+
+        // Called once for setting the initial separate profile credentials and not again during
+        // unification.
+        verify(mRecoverableKeyStoreManager)
+                .lockScreenSecretChanged(anyInt(), any(), eq(MANAGED_PROFILE_USER_ID));
+    }
+
+    public void testVerifyCredential_forPrimaryUser_sendsCredentials() throws Exception {
+        final String password = "password";
+        initializeStorageWithCredential(PRIMARY_USER_ID, password, CREDENTIAL_TYPE_PASSWORD, 1234);
+        reset(mRecoverableKeyStoreManager);
+
+        mService.verifyCredential(
+                password.getBytes(), CREDENTIAL_TYPE_PASSWORD, 1, PRIMARY_USER_ID);
+
+        verify(mRecoverableKeyStoreManager)
+                .lockScreenSecretAvailable(
+                        CREDENTIAL_TYPE_PASSWORD, password.getBytes(), PRIMARY_USER_ID);
+    }
+
+    public void testVerifyCredential_forProfileWithSeparateChallenge_sendsCredentials()
+            throws Exception {
+        final byte[] pattern = "12345".getBytes();
+        mService.setLockCredential(
+                pattern,
+                CREDENTIAL_TYPE_PATTERN,
+                null,
+                PASSWORD_QUALITY_SOMETHING,
+                MANAGED_PROFILE_USER_ID,
+                false);
+        reset(mRecoverableKeyStoreManager);
+
+        mService.verifyCredential(pattern, CREDENTIAL_TYPE_PATTERN, 1, MANAGED_PROFILE_USER_ID);
+
+        verify(mRecoverableKeyStoreManager)
+                .lockScreenSecretAvailable(
+                        CREDENTIAL_TYPE_PATTERN, pattern, MANAGED_PROFILE_USER_ID);
+    }
+
+    public void
+            testVerifyCredential_forPrimaryUserWithUnifiedChallengeProfile_sendsCredentialsForBoth()
+                    throws Exception {
+        final String pattern = "12345";
+        initializeStorageWithCredential(PRIMARY_USER_ID, pattern, CREDENTIAL_TYPE_PATTERN, 1234);
+        mService.setSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID, false, null);
+        reset(mRecoverableKeyStoreManager);
+
+        mService.verifyCredential(pattern.getBytes(), CREDENTIAL_TYPE_PATTERN, 1, PRIMARY_USER_ID);
+
+        // Parent sends its credentials for both the parent and profile.
+        verify(mRecoverableKeyStoreManager)
+                .lockScreenSecretAvailable(
+                        CREDENTIAL_TYPE_PATTERN, pattern.getBytes(), PRIMARY_USER_ID);
+        verify(mRecoverableKeyStoreManager)
+                .lockScreenSecretAvailable(
+                        CREDENTIAL_TYPE_PATTERN, pattern.getBytes(), MANAGED_PROFILE_USER_ID);
+        // Profile doesn't send its own random credentials.
+        verify(mRecoverableKeyStoreManager, never())
+                .lockScreenSecretAvailable(
+                        eq(CREDENTIAL_TYPE_PASSWORD), any(), eq(MANAGED_PROFILE_USER_ID));
+    }
+
     private void testCreateCredential(int userId, String credential, int type, int quality)
             throws RemoteException {
         mService.setLockCredential(credential.getBytes(), type, null, quality,
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 5bab65c..6890017 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -58,6 +58,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.widget.LockPatternUtils;
 import com.android.server.locksettings.recoverablekeystore.storage.ApplicationKeyStorage;
 import com.android.server.locksettings.recoverablekeystore.storage.CleanupManager;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
@@ -83,7 +84,7 @@
 import java.util.ArrayList;
 import java.util.Map;
 import java.util.Random;
-import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
 
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
@@ -156,6 +157,7 @@
     @Mock private PlatformKeyManager mPlatformKeyManager;
     @Mock private ApplicationKeyStorage mApplicationKeyStorage;
     @Mock private CleanupManager mCleanupManager;
+    @Mock private ExecutorService mExecutorService;
     @Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
 
     private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
@@ -188,7 +190,7 @@
                 mMockContext,
                 mRecoverableKeyStoreDb,
                 mRecoverySessionStorage,
-                Executors.newSingleThreadExecutor(),
+                mExecutorService,
                 mRecoverySnapshotStorage,
                 mMockListenersStorage,
                 mPlatformKeyManager,
@@ -1246,6 +1248,24 @@
         }
     }
 
+    @Test
+    public void lockScreenSecretAvailable_syncsKeysForUser() throws Exception {
+        mRecoverableKeyStoreManager.lockScreenSecretAvailable(
+                LockPatternUtils.CREDENTIAL_TYPE_PATTERN, "password".getBytes(), 11);
+
+        verify(mExecutorService).execute(any());
+    }
+
+    @Test
+    public void lockScreenSecretChanged_syncsKeysForUser() throws Exception {
+        mRecoverableKeyStoreManager.lockScreenSecretChanged(
+                LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
+                "password".getBytes(),
+                11);
+
+        verify(mExecutorService).execute(any());
+    }
+
     private static byte[] encryptedApplicationKey(
             SecretKey recoveryKey, byte[] applicationKey) throws Exception {
         return KeySyncUtils.encryptKeysWithRecoveryKey(recoveryKey, ImmutableMap.of(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 6f1bd87f..ca7a71e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -18,6 +18,8 @@
 
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.app.Notification.CATEGORY_CALL;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
 import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
@@ -81,6 +83,7 @@
 import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.Person;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.usage.UsageStatsManagerInternal;
 import android.companion.ICompanionDeviceManager;
@@ -103,6 +106,7 @@
 import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.DeviceConfig;
@@ -433,6 +437,11 @@
 
     private NotificationRecord generateNotificationRecord(NotificationChannel channel,
             Notification.TvExtender extender) {
+        return generateNotificationRecord(channel, extender, false /* isBubble */);
+    }
+
+    private NotificationRecord generateNotificationRecord(NotificationChannel channel,
+            Notification.TvExtender extender, boolean isBubble) {
         if (channel == null) {
             channel = mTestNotificationChannel;
         }
@@ -442,6 +451,9 @@
         if (extender != null) {
             nb.extend(extender);
         }
+        if (isBubble) {
+            nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build());
+        }
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
                 nb.build(), new UserHandle(mUid), null, 0);
         return new NotificationRecord(mContext, sbn, channel);
@@ -4293,7 +4305,7 @@
     }
 
     @Test
-    public void testFlagBubbleNotifs_flagIfAllowed() throws RemoteException {
+    public void testFlagBubbleNotifs_flag_appForeground() throws RemoteException {
         // Bubbles are allowed!
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
@@ -4303,12 +4315,260 @@
         when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
                 mTestNotificationChannel.getImportance());
 
-        // Notif with bubble metadata
+        // Notif with bubble metadata but not our other misc requirements
+        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
+                null /* tvExtender */, true /* isBubble */);
+
+        // Say we're foreground
+        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_FOREGROUND);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // yes allowed, yes foreground, yes bubble
+        assertTrue(mService.getNotificationRecord(
+                nr.sbn.getKey()).getNotification().isBubbleNotification());
+    }
+
+    @Test
+    public void testFlagBubbleNotifs_noFlag_appNotForeground() throws RemoteException {
+        // Bubbles are allowed!
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
+        when(mPreferencesHelper.getNotificationChannel(
+                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                mTestNotificationChannel);
+        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
+                mTestNotificationChannel.getImportance());
+
+        // Notif with bubble metadata but not our other misc requirements
+        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
+                null /* tvExtender */, true /* isBubble */);
+
+        // Make sure we're NOT foreground
+        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_VISIBLE);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // yes allowed but NOT foreground, no bubble
+        assertFalse(mService.getNotificationRecord(
+                nr.sbn.getKey()).getNotification().isBubbleNotification());
+    }
+
+    @Test
+    public void testFlagBubbleNotifs_flag_previousForegroundFlag() throws RemoteException {
+        // Bubbles are allowed!
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
+        when(mPreferencesHelper.getNotificationChannel(
+                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                mTestNotificationChannel);
+        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
+                mTestNotificationChannel.getImportance());
+
+        // Notif with bubble metadata but not our other misc requirements
+        NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                null /* tvExtender */, true /* isBubble */);
+
+        // Send notif when we're foreground
+        when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_FOREGROUND);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId());
+        waitForIdle();
+
+        // yes allowed, yes foreground, yes bubble
+        assertTrue(mService.getNotificationRecord(
+                nr1.sbn.getKey()).getNotification().isBubbleNotification());
+
+        // Send a new update when we're not foreground
+        NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel,
+                null /* tvExtender */, true /* isBubble */);
+
+        when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_VISIBLE);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
+        waitForIdle();
+
+        // yes allowed, previously foreground / flagged, yes bubble
+        assertTrue(mService.getNotificationRecord(
+                nr2.sbn.getKey()).getNotification().isBubbleNotification());
+
+        StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifs2.length);
+        assertEquals(1, mService.getNotificationRecordCount());
+    }
+
+    @Test
+    public void testFlagBubbleNotifs_noFlag_previousForegroundFlag_afterRemoval()
+            throws RemoteException {
+        // Bubbles are allowed!
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
+        when(mPreferencesHelper.getNotificationChannel(
+                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                mTestNotificationChannel);
+        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
+                mTestNotificationChannel.getImportance());
+
+        // Notif with bubble metadata but not our other misc requirements
+        NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                null /* tvExtender */, true /* isBubble */);
+
+        // Send notif when we're foreground
+        when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_FOREGROUND);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId());
+        waitForIdle();
+
+        // yes allowed, yes foreground, yes bubble
+        assertTrue(mService.getNotificationRecord(
+                nr1.sbn.getKey()).getNotification().isBubbleNotification());
+
+        // Remove the bubble
+        mBinderService.cancelNotificationWithTag(PKG, "tag", nr1.sbn.getId(),
+                nr1.sbn.getUserId());
+        waitForIdle();
+
+        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+        assertEquals(0, notifs.length);
+        assertEquals(0, mService.getNotificationRecordCount());
+
+        // Send a new update when we're not foreground
+        NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel,
+                null /* tvExtender */, true /* isBubble */);
+
+        when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_VISIBLE);
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
+        waitForIdle();
+
+        // yes allowed, but was removed & no foreground, so no bubble
+        assertFalse(mService.getNotificationRecord(
+                nr2.sbn.getKey()).getNotification().isBubbleNotification());
+
+        StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifs2.length);
+        assertEquals(1, mService.getNotificationRecordCount());
+    }
+
+    @Test
+    public void testFlagBubbleNotifs_flag_messaging() throws RemoteException {
+        // Bubbles are allowed!
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
+        when(mPreferencesHelper.getNotificationChannel(
+                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                mTestNotificationChannel);
+        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
+                mTestNotificationChannel.getImportance());
+
+        // Give it bubble metadata
         Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // Make it messaging style
         Notification.Builder nb = new Notification.Builder(mContext,
                 mTestNotificationChannel.getId())
                 .setContentTitle("foo")
                 .setBubbleMetadata(data)
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person)
+                )
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // yes allowed, yes messaging, yes bubble
+        assertTrue(mService.getNotificationRecord(
+                sbn.getKey()).getNotification().isBubbleNotification());
+    }
+
+    @Test
+    public void testFlagBubbleNotifs_flag_phonecall() throws RemoteException {
+        // Bubbles are allowed!
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
+        when(mPreferencesHelper.getNotificationChannel(
+                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                mTestNotificationChannel);
+        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
+                mTestNotificationChannel.getImportance());
+
+        // Give it bubble metadata
+        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // Make it a phone call
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setCategory(CATEGORY_CALL)
+                .addPerson(person)
+                .setContentTitle("foo")
+                .setBubbleMetadata(data)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        // Make sure it has foreground service
+        sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // yes phone call, yes person, yes foreground service, yes bubble
+        assertTrue(mService.getNotificationRecord(
+                sbn.getKey()).getNotification().isBubbleNotification());
+    }
+
+    @Test
+    public void testFlagBubbleNotifs_noFlag_phonecall_noForegroundService() throws RemoteException {
+        // Bubbles are allowed!
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
+        when(mPreferencesHelper.getNotificationChannel(
+                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                mTestNotificationChannel);
+        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
+                mTestNotificationChannel.getImportance());
+
+        // Give it bubble metadata
+        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // Make it a phone call
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setCategory(CATEGORY_CALL)
+                .addPerson(person)
+                .setContentTitle("foo")
+                .setBubbleMetadata(data)
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
@@ -4319,13 +4579,89 @@
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
-        // yes allowed, yes bubble
-        assertTrue(mService.getNotificationRecord(
+        // yes phone call, yes person, NO foreground service, no bubble
+        assertFalse(mService.getNotificationRecord(
                 sbn.getKey()).getNotification().isBubbleNotification());
     }
 
     @Test
-    public void testFlagBubbleNotifs_noFlagIfNotAllowed() throws RemoteException {
+    public void testFlagBubbleNotifs_noFlag_phonecall_noPerson() throws RemoteException {
+        // Bubbles are allowed!
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
+        when(mPreferencesHelper.getNotificationChannel(
+                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                mTestNotificationChannel);
+        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
+                mTestNotificationChannel.getImportance());
+
+        // Give it bubble metadata
+        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        // Make it a phone call
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setCategory(CATEGORY_CALL)
+                .setContentTitle("foo")
+                .setBubbleMetadata(data)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        // Make sure it has foreground service
+        sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // yes phone call, yes foreground service, BUT NO person, no bubble
+        assertFalse(mService.getNotificationRecord(
+                sbn.getKey()).getNotification().isBubbleNotification());
+    }
+
+    @Test
+    public void testFlagBubbleNotifs_noFlag_phonecall_noCategory() throws RemoteException {
+        // Bubbles are allowed!
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
+        when(mPreferencesHelper.getNotificationChannel(
+                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                mTestNotificationChannel);
+        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
+                mTestNotificationChannel.getImportance());
+
+        // Give it bubble metadata
+        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // No category
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .addPerson(person)
+                .setContentTitle("foo")
+                .setBubbleMetadata(data)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        // Make sure it has foreground service
+        sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // yes person, yes foreground service, BUT NO call, no bubble
+        assertFalse(mService.getNotificationRecord(
+                sbn.getKey()).getNotification().isBubbleNotification());
+    }
+
+    @Test
+    public void testFlagBubbleNotifs_noFlag_messaging_appNotAllowed() throws RemoteException {
         // Bubbles are NOT allowed!
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(false);
@@ -4335,12 +4671,24 @@
         when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
                 mTestNotificationChannel.getImportance());
 
-        // Notif with bubble metadata
+        // Give it bubble metadata
         Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // Make it messaging style
         Notification.Builder nb = new Notification.Builder(mContext,
                 mTestNotificationChannel.getId())
                 .setContentTitle("foo")
                 .setBubbleMetadata(data)
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person)
+                )
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
@@ -4358,7 +4706,7 @@
     }
 
     @Test
-    public void testFlagBubbleNotifs_noFlagIfNotBubble() throws RemoteException {
+    public void testFlagBubbleNotifs_noFlag_notBubble() throws RemoteException {
         // Bubbles are allowed!
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
@@ -4369,27 +4717,20 @@
                 mTestNotificationChannel.getImportance());
 
         // Notif WITHOUT bubble metadata
-        Notification.Builder nb = new Notification.Builder(mContext,
-                mTestNotificationChannel.getId())
-                .setContentTitle("foo")
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
-        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
-                nb.build(), new UserHandle(mUid), null, 0);
-        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
 
         // Post the notification
-        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
                 nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
         waitForIdle();
 
         // no bubble metadata, no bubble
         assertFalse(mService.getNotificationRecord(
-                sbn.getKey()).getNotification().isBubbleNotification());
+                nr.sbn.getKey()).getNotification().isBubbleNotification());
     }
 
     @Test
-    public void testFlagBubbleNotifs_noFlagIfChannelNotBubble() throws RemoteException {
+    public void testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed() throws RemoteException {
         // Bubbles are allowed!
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
@@ -4402,12 +4743,24 @@
         // But not on this channel!
         mTestNotificationChannel.setAllowBubbles(false);
 
-        // Notif with bubble metadata
+        // Give it bubble metadata
         Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // Make it messaging style
         Notification.Builder nb = new Notification.Builder(mContext,
                 mTestNotificationChannel.getId())
                 .setContentTitle("foo")
                 .setBubbleMetadata(data)
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person)
+                )
                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
 
         StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
@@ -4425,6 +4778,91 @@
     }
 
     @Test
+    public void testFlagBubbleNotifs_noFlag_phonecall_notAllowed() throws RemoteException {
+        // Bubbles are allowed!
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(false);
+        when(mPreferencesHelper.getNotificationChannel(
+                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                mTestNotificationChannel);
+        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
+                mTestNotificationChannel.getImportance());
+
+        // Give it bubble metadata
+        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // Make it a phone call
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setCategory(CATEGORY_CALL)
+                .addPerson(person)
+                .setContentTitle("foo")
+                .setBubbleMetadata(data)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        // Make sure it has foreground service
+        sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // yes phone call, yes person, yes foreground service, but not allowed, no bubble
+        assertFalse(mService.getNotificationRecord(
+                sbn.getKey()).getNotification().isBubbleNotification());
+    }
+
+    @Test
+    public void testFlagBubbleNotifs_noFlag_phonecall_channelNotAllowed() throws RemoteException {
+        // Bubbles are allowed!
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(false);
+        when(mPreferencesHelper.getNotificationChannel(
+                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                mTestNotificationChannel);
+        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
+                mTestNotificationChannel.getImportance());
+
+        // But not on this channel!
+        mTestNotificationChannel.setAllowBubbles(false);
+
+        // Give it bubble metadata
+        Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+        // Give it a person
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        // Make it a phone call
+        Notification.Builder nb = new Notification.Builder(mContext,
+                mTestNotificationChannel.getId())
+                .setCategory(CATEGORY_CALL)
+                .addPerson(person)
+                .setContentTitle("foo")
+                .setBubbleMetadata(data)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
+                nb.build(), new UserHandle(mUid), null, 0);
+        // Make sure it has foreground service
+        sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        // yes phone call, yes person, yes foreground service, but channel not allowed, no bubble
+        assertFalse(mService.getNotificationRecord(
+                sbn.getKey()).getNotification().isBubbleNotification());
+    }
+
+    @Test
     public void testCancelAllNotifications_cancelsBubble() throws Exception {
         final NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
         nr.sbn.getNotification().flags |= FLAG_BUBBLE;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 87f10a4..6ed78b3 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -2124,9 +2124,16 @@
 
     @Test
     public void testXml_statusBarIcons_default() throws Exception {
-        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL);
+        String preQXml = "<ranking version=\"1\">\n"
+                + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
+                + "<channel id=\"something\" name=\"name\" importance=\"2\" "
+                + "show_badge=\"true\" />\n"
+                + "<channel id=\"miscellaneous\" name=\"Uncategorized\" usage=\"5\" "
+                + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+                + "</package>\n"
+                + "</ranking>\n";
         mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
-        loadStreamXml(baos, false, UserHandle.USER_ALL);
+        loadByteArrayXml(preQXml.getBytes(), true, UserHandle.USER_SYSTEM);
 
         assertEquals(PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS,
                 mHelper.shouldHideSilentStatusIcons());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 08e6ce4..af04858 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -625,7 +625,7 @@
         // excludedTask is not trimmed.
         assertTrimmed(mTasks.get(0));
 
-        mRecentTasks.removeAllVisibleTasks();
+        mRecentTasks.removeAllVisibleTasks(TEST_USER_0_ID);
 
         // Only visible tasks removed.
         assertTrimmed(mTasks.get(0), mTasks.get(1), mTasks.get(2), mTasks.get(3));
@@ -849,11 +849,31 @@
         mRecentTasks.add(t7);
 
         // Remove all the visible tasks and ensure that they are removed
-        mRecentTasks.removeAllVisibleTasks();
+        mRecentTasks.removeAllVisibleTasks(TEST_USER_0_ID);
         assertTrimmed(t1, t2, t3, t4, t5, t6, t7);
     }
 
     @Test
+    public void testRemoveAllVisibleTasksPerUser() {
+        mRecentTasks.setParameters(-1 /* min */, 3 /* max */, 100 /* ms */);
+
+        // Create a visible task per user
+        TaskRecord t1 = createTaskBuilder(".Task1")
+                .setUserId(TEST_USER_0_ID)
+                .build();
+        mRecentTasks.add(t1);
+
+        TaskRecord t2 = createTaskBuilder(".Task1")
+                .setUserId(TEST_USER_1_ID)
+                .build();
+        mRecentTasks.add(t2);
+
+        // Remove all the visible tasks and ensure that they are removed
+        mRecentTasks.removeAllVisibleTasks(TEST_USER_0_ID);
+        assertTrimmed(t1);
+    }
+
+    @Test
     public void testNotRestoreRecentTaskApis() {
         final TaskRecord task = createTaskBuilder(".Task").build();
         final int taskId = task.taskId;
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index c05a346..1822cee 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -552,7 +552,6 @@
         private final Bundle mExtras;
         private final Bundle mIntentExtras;
         private final long mCreationTimeMillis;
-        private final CallIdentification mCallIdentification;
         private final @CallDirection int mCallDirection;
 
         /**
@@ -738,8 +737,6 @@
          * The display name for the caller.
          * <p>
          * This is the name as reported by the {@link ConnectionService} associated with this call.
-         * The name reported by a {@link CallScreeningService} can be retrieved using
-         * {@link CallIdentification#getName()}.
          *
          * @return The display name for the caller.
          */
@@ -857,23 +854,6 @@
         }
 
         /**
-         * Returns {@link CallIdentification} information provided by a
-         * {@link CallScreeningService} for this call.
-         * <p>
-         * {@link InCallService} implementations should display the {@link CallIdentification} for
-         * calls.  The name of the call screening service is provided in
-         * {@link CallIdentification#getCallScreeningAppName()} and should be used to attribute the
-         * call identification information.
-         *
-         * @return The {@link CallIdentification} if it was provided by a
-         * {@link CallScreeningService}, or {@code null} if no {@link CallScreeningService} has
-         * provided {@link CallIdentification} information for the call.
-         */
-        public @Nullable CallIdentification getCallIdentification() {
-            return mCallIdentification;
-        }
-
-        /**
          * Indicates whether the call is an incoming or outgoing call.
          * @return The call's direction.
          */
@@ -902,7 +882,6 @@
                         areBundlesEqual(mExtras, d.mExtras) &&
                         areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
                         Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) &&
-                        Objects.equals(mCallIdentification, d.mCallIdentification) &&
                         Objects.equals(mCallDirection, d.mCallDirection);
             }
             return false;
@@ -925,7 +904,6 @@
                             mExtras,
                             mIntentExtras,
                             mCreationTimeMillis,
-                            mCallIdentification,
                             mCallDirection);
         }
 
@@ -947,7 +925,6 @@
                 Bundle extras,
                 Bundle intentExtras,
                 long creationTimeMillis,
-                CallIdentification callIdentification,
                 int callDirection) {
             mTelecomCallId = telecomCallId;
             mHandle = handle;
@@ -965,7 +942,6 @@
             mExtras = extras;
             mIntentExtras = intentExtras;
             mCreationTimeMillis = creationTimeMillis;
-            mCallIdentification = callIdentification;
             mCallDirection = callDirection;
         }
 
@@ -988,7 +964,6 @@
                     parcelableCall.getExtras(),
                     parcelableCall.getIntentExtras(),
                     parcelableCall.getCreationTimeMillis(),
-                    parcelableCall.getCallIdentification(),
                     parcelableCall.getCallDirection());
         }
 
diff --git a/telecomm/java/android/telecom/CallIdentification.java b/telecomm/java/android/telecom/CallIdentification.java
deleted file mode 100644
index 745affd..0000000
--- a/telecomm/java/android/telecom/CallIdentification.java
+++ /dev/null
@@ -1,451 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telecom;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.pm.ApplicationInfo;
-import android.graphics.drawable.Icon;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-/**
- * Encapsulates information about an incoming or outgoing {@link Call} provided by a
- * {@link CallScreeningService}.
- * <p>
- * Call identified information is consumed by the {@link InCallService dialer} app to provide the
- * user with more information about a call.  This can include information such as the name of the
- * caller, address, etc.  Call identification information is persisted to the
- * {@link android.provider.CallLog}.
- */
-public final class CallIdentification implements Parcelable {
-    /**
-     * Builder for {@link CallIdentification} instances.
-     * <p>
-     * A {@link CallScreeningService} uses this class to create new instances of
-     * {@link CallIdentification} for a screened call.
-     */
-    public final static class Builder {
-        private CharSequence mName;
-        private CharSequence mDescription;
-        private CharSequence mDetails;
-        private Icon mPhoto;
-        private int mNuisanceConfidence = CallIdentification.CONFIDENCE_UNKNOWN;
-        private String mPackageName;
-        private CharSequence mAppName;
-
-        /**
-         * Default builder constructor.
-         */
-        public Builder() {
-            // Default constructor
-        }
-
-        /**
-         * Create instance of call identification with specified package/app name.
-         *
-         * @param callIdPackageName The package name.
-         * @param callIdAppName The app name.
-         * @hide
-         */
-        public Builder(@NonNull String callIdPackageName, @NonNull CharSequence callIdAppName) {
-            mPackageName = callIdPackageName;
-            mAppName = callIdAppName;
-        }
-
-        /**
-         * Sets the name associated with the {@link CallIdentification} being built.
-         * <p>
-         * Could be a business name, for example.
-         *
-         * @param name The name associated with the call, or {@code null} if none is provided.
-         * @return Builder instance.
-         */
-        public @NonNull Builder setName(@Nullable CharSequence name) {
-            mName = name;
-            return this;
-        }
-
-        /**
-         * Sets the description associated with the {@link CallIdentification} being built.
-         * <p>
-         * A description of the call as identified by a {@link CallScreeningService}.  The
-         * description is typically presented by Dialer apps after the
-         * {@link CallIdentification#getName() name} to provide a short piece of relevant
-         * information about the call.  This could include a location, address, or a message
-         * regarding the potential nature of the call (e.g. potential telemarketer).
-         *
-         * @param description The call description, or {@code null} if none is provided.
-         * @return Builder instance.
-         */
-        public @NonNull Builder setDescription(@Nullable CharSequence description) {
-            mDescription = description;
-            return this;
-        }
-
-        /**
-         * Sets the details associated with the {@link CallIdentification} being built.
-         * <p>
-         * The details is typically presented by Dialer apps after the
-         * {@link CallIdentification#getName() name} and
-         * {@link CallIdentification#getDescription() description} to provide further clarifying
-         * information about the call. This could include, for example, the opening hours of a
-         * business, or a stats about the number of times a call has been reported as spam.
-         *
-         * @param details The call details, or {@code null} if none is provided.
-         * @return Builder instance.
-         */
-
-        public @NonNull Builder setDetails(@Nullable CharSequence details) {
-            mDetails = details;
-            return this;
-        }
-
-        /**
-         * Sets the photo associated with the {@link CallIdentification} being built.
-         * <p>
-         * This could be, for example, a business logo, or a photo of the caller.
-         *
-         * @param photo The photo associated with the call, or {@code null} if none was provided.
-         * @return Builder instance.
-         */
-        public @NonNull Builder setPhoto(@Nullable Icon photo) {
-            mPhoto = photo;
-            return this;
-        }
-
-        /**
-         * Sets the nuisance confidence with the {@link CallIdentification} being built.
-         * <p>
-         * This can be used to specify how confident the {@link CallScreeningService} is that a call
-         * is or is not a nuisance call.
-         *
-         * @param nuisanceConfidence The nuisance confidence.
-         * @return The builder.
-         */
-        public @NonNull Builder setNuisanceConfidence(@NuisanceConfidence int nuisanceConfidence) {
-            mNuisanceConfidence = nuisanceConfidence;
-            return this;
-        }
-
-        /**
-         * Creates a new instance of {@link CallIdentification} based on the parameters set in this
-         * builder.
-         *
-         * @return {@link CallIdentification} instance.
-         */
-        public @NonNull CallIdentification build() {
-            return new CallIdentification(mName, mDescription, mDetails, mPhoto,
-                    mNuisanceConfidence, mPackageName, mAppName);
-        }
-    }
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            prefix = { "CONFIDENCE_" },
-            value = {CONFIDENCE_NUISANCE, CONFIDENCE_LIKELY_NUISANCE, CONFIDENCE_UNKNOWN,
-                    CONFIDENCE_LIKELY_NOT_NUISANCE, CONFIDENCE_NOT_NUISANCE})
-    public @interface NuisanceConfidence {}
-
-    /**
-     * Call has been identified as a nuisance call.
-     * <p>
-     * Returned from {@link #getNuisanceConfidence()} to indicate that a
-     * {@link CallScreeningService} to indicate how confident it is that a call is or is not a
-     * nuisance call.
-     */
-    public static final int CONFIDENCE_NUISANCE = 2;
-
-    /**
-     * Call has been identified as a likely nuisance call.
-     * <p>
-     * Returned from {@link #getNuisanceConfidence()} to indicate that a
-     * {@link CallScreeningService} to indicate how confident it is that a call is or is not a
-     * nuisance call.
-     */
-    public static final int CONFIDENCE_LIKELY_NUISANCE = 1;
-
-    /**
-     * Call could not be classified as nuisance or non-nuisance.
-     * <p>
-     * Returned from {@link #getNuisanceConfidence()} to indicate that a
-     * {@link CallScreeningService} to indicate how confident it is that a call is or is not a
-     * nuisance call.
-     */
-    public static final int CONFIDENCE_UNKNOWN = 0;
-
-    /**
-     * Call has been identified as not likely to be a nuisance call.
-     * <p>
-     * Returned from {@link #getNuisanceConfidence()} to indicate that a
-     * {@link CallScreeningService} to indicate how confident it is that a call is or is not a
-     * nuisance call.
-     */
-    public static final int CONFIDENCE_LIKELY_NOT_NUISANCE = -1;
-
-    /**
-     * Call has been identified as not a nuisance call.
-     * <p>
-     * Returned from {@link #getNuisanceConfidence()} to indicate that a
-     * {@link CallScreeningService} to indicate how confident it is that a call is or is not a
-     * nuisance call.
-     */
-    public static final int CONFIDENCE_NOT_NUISANCE = -2;
-
-    /**
-     * Default constructor for {@link CallIdentification}.
-     *
-     * @param name The name.
-     * @param description The description.
-     * @param details The details.
-     * @param photo The photo.
-     * @param nuisanceConfidence Confidence that this is a nuisance call.
-     * @hide
-     */
-    private CallIdentification(@Nullable String name, @Nullable String description,
-            @Nullable String details, @Nullable Icon photo,
-            @NuisanceConfidence int nuisanceConfidence) {
-        this(name, description, details, photo, nuisanceConfidence, null, null);
-    }
-
-    /**
-     * Default constructor for {@link CallIdentification}.
-     *
-     * @param name The name.
-     * @param description The description.
-     * @param details The details.
-     * @param photo The photo.
-     * @param nuisanceConfidence Confidence that this is a nuisance call.
-     * @param callScreeningPackageName Package name of the {@link CallScreeningService} which
-     *                                 provided the call identification.
-     * @param callScreeningAppName App name of the {@link CallScreeningService} which provided the
-     *                             call identification.
-     * @hide
-     */
-    private CallIdentification(@Nullable CharSequence name, @Nullable CharSequence description,
-            @Nullable CharSequence details, @Nullable Icon photo,
-            @NuisanceConfidence int nuisanceConfidence, @NonNull String callScreeningPackageName,
-            @NonNull CharSequence callScreeningAppName) {
-        mName = name;
-        mDescription = description;
-        mDetails = details;
-        mPhoto = photo;
-        mNuisanceConfidence = nuisanceConfidence;
-        mCallScreeningAppName = callScreeningAppName;
-        mCallScreeningPackageName = callScreeningPackageName;
-    }
-
-    private CharSequence mName;
-    private CharSequence mDescription;
-    private CharSequence mDetails;
-    private Icon mPhoto;
-    private int mNuisanceConfidence;
-    private String mCallScreeningPackageName;
-    private CharSequence mCallScreeningAppName;
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel parcel, int i) {
-        parcel.writeCharSequence(mName);
-        parcel.writeCharSequence(mDescription);
-        parcel.writeCharSequence(mDetails);
-        parcel.writeParcelable(mPhoto, 0);
-        parcel.writeInt(mNuisanceConfidence);
-        parcel.writeString(mCallScreeningPackageName);
-        parcel.writeCharSequence(mCallScreeningAppName);
-    }
-
-    /**
-     * Responsible for creating CallIdentification objects for deserialized Parcels.
-     */
-    public static final @android.annotation.NonNull Parcelable.Creator<CallIdentification> CREATOR =
-            new Parcelable.Creator<CallIdentification> () {
-
-                @Override
-                public CallIdentification createFromParcel(Parcel source) {
-                    CharSequence name = source.readCharSequence();
-                    CharSequence description = source.readCharSequence();
-                    CharSequence details = source.readCharSequence();
-                    Icon photo = source.readParcelable(ClassLoader.getSystemClassLoader());
-                    int nuisanceConfidence = source.readInt();
-                    String callScreeningPackageName = source.readString();
-                    CharSequence callScreeningAppName = source.readCharSequence();
-                    return new CallIdentification(name, description, details, photo,
-                            nuisanceConfidence, callScreeningPackageName, callScreeningAppName);
-                }
-
-                @Override
-                public CallIdentification[] newArray(int size) {
-                    return new CallIdentification[size];
-                }
-            };
-
-    /**
-     * The name associated with the number.
-     * <p>
-     * The name of the call as identified by a {@link CallScreeningService}.  Could be a business
-     * name, for example.
-     *
-     * @return The name associated with the number, or {@code null} if none was provided.
-     */
-    public final @Nullable CharSequence getName() {
-        return mName;
-    }
-
-    /**
-     * Description of the call.
-     * <p>
-     * A description of the call as identified by a {@link CallScreeningService}.  The description
-     * is typically presented by Dialer apps after the {@link #getName() name} to provide a short
-     * piece of relevant information about the call.  This could include a location, address, or a
-     * message regarding the potential nature of the call (e.g. potential telemarketer).
-     *
-     * @return The call description, or {@code null} if none was provided.
-     */
-    public final @Nullable CharSequence getDescription() {
-        return mDescription;
-    }
-
-    /**
-     * Details of the call.
-     * <p>
-     * Details of the call as identified by a {@link CallScreeningService}.  The details
-     * are typically presented by Dialer apps after the {@link #getName() name} and
-     * {@link #getDescription() description} to provide further clarifying information about the
-     * call. This could include, for example, the opening hours of a business, or stats about
-     * the number of times a call has been reported as spam.
-     *
-     * @return The call details, or {@code null} if none was provided.
-     */
-    public final @Nullable CharSequence getDetails() {
-        return mDetails;
-    }
-
-    /**
-     * Photo associated with the call.
-     * <p>
-     * A photo associated with the call as identified by a {@link CallScreeningService}.  This
-     * could be, for example, a business logo, or a photo of the caller.
-     *
-     * @return The photo associated with the call, or {@code null} if none was provided.
-     */
-    public final @Nullable Icon getPhoto() {
-        return mPhoto;
-    }
-
-    /**
-     * Indicates the likelihood that this call is a nuisance call.
-     * <p>
-     * How likely the call is a nuisance call, as identified by a {@link CallScreeningService}.
-     *
-     * @return The nuisance confidence.
-     */
-    public final @NuisanceConfidence int getNuisanceConfidence() {
-        return mNuisanceConfidence;
-    }
-
-    /**
-     * The package name of the {@link CallScreeningService} which provided the
-     * {@link CallIdentification}.
-     * <p>
-     * A {@link CallScreeningService} may not set this property; it is set by the system.
-     * @return the package name
-     */
-    public final @NonNull String getCallScreeningPackageName() {
-        return mCallScreeningPackageName;
-    }
-
-    /**
-     * The {@link android.content.pm.PackageManager#getApplicationLabel(ApplicationInfo) name} of
-     * the {@link CallScreeningService} which provided the {@link CallIdentification}.
-     * <p>
-     * A {@link CallScreeningService} may not set this property; it is set by the system.
-     *
-     * @return The name of the app.
-     */
-    public final @NonNull CharSequence getCallScreeningAppName() {
-        return mCallScreeningAppName;
-    }
-
-    /**
-     * Set the package name of the {@link CallScreeningService} which provided this information.
-     *
-     * @param callScreeningPackageName The package name.
-     * @hide
-     */
-    public void setCallScreeningPackageName(@NonNull String callScreeningPackageName) {
-        mCallScreeningPackageName = callScreeningPackageName;
-    }
-
-    /**
-     * Set the app name of the {@link CallScreeningService} which provided this information.
-     *
-     * @param callScreeningAppName The app name.
-     * @hide
-     */
-    public void setCallScreeningAppName(@NonNull CharSequence callScreeningAppName) {
-        mCallScreeningAppName = callScreeningAppName;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        CallIdentification that = (CallIdentification) o;
-        // Note: mPhoto purposely omit as no good comparison exists.
-        return mNuisanceConfidence == that.mNuisanceConfidence
-                && Objects.equals(mName, that.mName)
-                && Objects.equals(mDescription, that.mDescription)
-                && Objects.equals(mDetails, that.mDetails)
-                && Objects.equals(mCallScreeningAppName, that.mCallScreeningAppName)
-                && Objects.equals(mCallScreeningPackageName, that.mCallScreeningPackageName);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(mName, mDescription, mDetails, mPhoto, mNuisanceConfidence,
-                mCallScreeningAppName, mCallScreeningPackageName);
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("[CallId mName=");
-        sb.append(Log.pii(mName));
-        sb.append(", mDesc=");
-        sb.append(mDescription);
-        sb.append(", mDet=");
-        sb.append(mDetails);
-        sb.append(", conf=");
-        sb.append(mNuisanceConfidence);
-        sb.append(", appName=");
-        sb.append(mCallScreeningAppName);
-        sb.append(", pkgName=");
-        sb.append(mCallScreeningPackageName);
-        return sb.toString();
-    }
-}
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index b1aece7..0e0406d 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -39,8 +39,8 @@
 /**
  * This service can be implemented by the default dialer (see
  * {@link TelecomManager#getDefaultDialerPackage()}) or a third party app to allow or disallow
- * incoming calls before they are shown to a user.  This service can also provide
- * {@link CallIdentification} information for calls.
+ * incoming calls before they are shown to a user. A {@link CallScreeningService} can also see
+ * outgoing calls for the purpose of providing caller ID services for those calls.
  * <p>
  * Below is an example manifest registration for a {@code CallScreeningService}.
  * <pre>
@@ -58,9 +58,9 @@
  * <ol>
  *     <li>Call blocking/screening - the service can choose which calls will ring on the user's
  *     device, and which will be silently sent to voicemail.</li>
- *     <li>Call identification - the service can optionally provide {@link CallIdentification}
- *     information about a {@link Call.Details call} which will be shown to the user in the
- *     Dialer app.</li>
+ *     <li>Call identification - services which provide call identification functionality can
+ *     display a user-interface of their choosing which contains identifying information for a call.
+ *     </li>
  * </ol>
  * <p>
  * <h2>Becoming the {@link CallScreeningService}</h2>
@@ -92,128 +92,6 @@
  * </pre>
  */
 public abstract class CallScreeningService extends Service {
-
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            prefix = { "CALL_DURATION_" },
-            value = {CALL_DURATION_VERY_SHORT, CALL_DURATION_SHORT, CALL_DURATION_MEDIUM,
-                    CALL_DURATION_LONG})
-    public @interface CallDuration {}
-
-    /**
-     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
-     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
-     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
-     * {@link CallScreeningService} can use this as a signal for training nuisance detection
-     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
-     * identifying call log information to the {@link CallScreeningService}.
-     * <p>
-     * Indicates the call was < 3 seconds in duration.
-     */
-    public static final int CALL_DURATION_VERY_SHORT = 1;
-
-    /**
-     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
-     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
-     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
-     * {@link CallScreeningService} can use this as a signal for training nuisance detection
-     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
-     * identifying call log information to the {@link CallScreeningService}.
-     * <p>
-     * Indicates the call was greater than 3 seconds, but less than 60 seconds in duration.
-     */
-    public static final int CALL_DURATION_SHORT = 2;
-
-    /**
-     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
-     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
-     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
-     * {@link CallScreeningService} can use this as a signal for training nuisance detection
-     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
-     * identifying call log information to the {@link CallScreeningService}.
-     * <p>
-     * Indicates the call was greater than 60 seconds, but less than 120 seconds in duration.
-     */
-    public static final int CALL_DURATION_MEDIUM = 3;
-
-    /**
-     * Call duration reported with {@link #EXTRA_CALL_DURATION} to indicate to the
-     * {@link CallScreeningService} the duration of a call for which the user reported the nuisance
-     * status (see {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).  The
-     * {@link CallScreeningService} can use this as a signal for training nuisance detection
-     * algorithms.  The call duration is reported in coarse grained buckets to minimize exposure of
-     * identifying call log information to the {@link CallScreeningService}.
-     * <p>
-     * Indicates the call was greater than 120 seconds.
-     */
-    public static final int CALL_DURATION_LONG = 4;
-
-    /**
-     * Telecom sends this intent to the {@link CallScreeningService} which the user has chosen to
-     * fill the call screening role when the user indicates through the default dialer whether a
-     * call is a nuisance call or not (see
-     * {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}).
-     * <p>
-     * The following extra values are provided for the call:
-     * <ol>
-     *     <li>{@link #EXTRA_CALL_HANDLE} - the handle of the call.</li>
-     *     <li>{@link #EXTRA_IS_NUISANCE} - {@code true} if the user reported the call as a nuisance
-     *     call, {@code false} otherwise.</li>
-     *     <li>{@link #EXTRA_CALL_TYPE} - reports the type of call (incoming, rejected, missed,
-     *     blocked).</li>
-     *     <li>{@link #EXTRA_CALL_DURATION} - the duration of the call (see
-     *     {@link #EXTRA_CALL_DURATION} for valid values).</li>
-     * </ol>
-     * <p>
-     * {@link CallScreeningService} implementations which want to track whether the user reports
-     * calls are nuisance calls should use declare a broadcast receiver in their manifest for this
-     * intent.
-     * <p>
-     * Note: Only {@link CallScreeningService} implementations which have provided
-     * {@link CallIdentification} information for calls at some point will receive this intent.
-     */
-    public static final String ACTION_NUISANCE_CALL_STATUS_CHANGED =
-            "android.telecom.action.NUISANCE_CALL_STATUS_CHANGED";
-
-    /**
-     * Extra used to provide the handle of the call for
-     * {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}.  The call handle is reported as a
-     * {@link Uri}.
-     */
-    public static final String EXTRA_CALL_HANDLE = "android.telecom.extra.CALL_HANDLE";
-
-    /**
-     * Boolean extra used to indicate whether the user reported a call as a nuisance call.
-     * When {@code true}, the user reported that a call was a nuisance call, {@code false}
-     * otherwise.  Sent with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED}.
-     */
-    public static final String EXTRA_IS_NUISANCE = "android.telecom.extra.IS_NUISANCE";
-
-    /**
-     * Integer extra used with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report the type of
-     * call. Valid values are:
-     * <UL>
-     *   <li>{@link android.provider.CallLog.Calls#MISSED_TYPE}</li>
-     *   <li>{@link android.provider.CallLog.Calls#INCOMING_TYPE}</li>
-     *   <li>{@link android.provider.CallLog.Calls#BLOCKED_TYPE}</li>
-     *   <li>{@link android.provider.CallLog.Calls#REJECTED_TYPE}</li>
-     * </UL>
-     */
-    public static final String EXTRA_CALL_TYPE = "android.telecom.extra.CALL_TYPE";
-
-    /**
-     * Integer extra used to with {@link #ACTION_NUISANCE_CALL_STATUS_CHANGED} to report how long
-     * the call lasted.  Valid values are:
-     * <UL>
-     *     <LI>{@link #CALL_DURATION_VERY_SHORT}</LI>
-     *     <LI>{@link #CALL_DURATION_SHORT}</LI>
-     *     <LI>{@link #CALL_DURATION_MEDIUM}</LI>
-     *     <LI>{@link #CALL_DURATION_LONG}</LI>
-     * </UL>
-     */
-    public static final String EXTRA_CALL_DURATION = "android.telecom.extra.CALL_DURATION";
-
     /**
      * The {@link Intent} that must be declared as handled by the service.
      */
@@ -413,10 +291,6 @@
      * Your app can tell if a call is an incoming call by checking to see if
      * {@link Call.Details#getCallDirection()} is {@link Call.Details#DIRECTION_INCOMING}.
      * <p>
-     * For incoming or outgoing calls, the {@link CallScreeningService} can call
-     * {@link #provideCallIdentification(Call.Details, CallIdentification)} in order to provide
-     * {@link CallIdentification} for the call.
-     * <p>
      * Note: The {@link Call.Details} instance provided to a call screening service will only have
      * the following properties set.  The rest of the {@link Call.Details} properties will be set to
      * their default value or {@code null}.
@@ -472,32 +346,4 @@
         } catch (RemoteException e) {
         }
     }
-
-    /**
-     * Provide {@link CallIdentification} information about a {@link Call.Details call}.
-     * <p>
-     * The {@link CallScreeningService} calls this method to provide information it has identified
-     * about a {@link Call.Details call}.  This information will potentially be shown to the user
-     * in the {@link InCallService dialer} app.  It will be logged to the
-     * {@link android.provider.CallLog}.
-     * <p>
-     * A {@link CallScreeningService} should only call this method for calls for which it is able to
-     * provide some {@link CallIdentification} for.  {@link CallIdentification} instances with no
-     * fields set will be ignored by the system.
-     *
-     * @param callDetails The call to provide information for.
-     *                    <p>
-     *                    Must be the same {@link Call.Details call} which was provided to the
-     *                    {@link CallScreeningService} via {@link #onScreenCall(Call.Details)}.
-     * @param identification An instance of {@link CallIdentification} with information about the
-     *                       {@link Call.Details call}.
-     */
-    public final void provideCallIdentification(@NonNull Call.Details callDetails,
-            @NonNull CallIdentification identification) {
-        try {
-            mCallScreeningAdapter.provideCallIdentification(callDetails.getTelecomCallId(),
-                    identification);
-        } catch (RemoteException e) {
-        }
-    }
 }
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index 345707e..aa50991 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -64,7 +64,6 @@
     private final Bundle mIntentExtras;
     private final Bundle mExtras;
     private final long mCreationTimeMillis;
-    private final CallIdentification mCallIdentification;
     private final int mCallDirection;
 
     public ParcelableCall(
@@ -94,7 +93,6 @@
             Bundle intentExtras,
             Bundle extras,
             long creationTimeMillis,
-            CallIdentification callIdentification,
             int callDirection) {
         mId = id;
         mState = state;
@@ -122,7 +120,6 @@
         mIntentExtras = intentExtras;
         mExtras = extras;
         mCreationTimeMillis = creationTimeMillis;
-        mCallIdentification = callIdentification;
         mCallDirection = callDirection;
     }
 
@@ -314,15 +311,6 @@
     }
 
     /**
-     * Contains call identification information returned by a {@link CallScreeningService}.
-     * @return The {@link CallIdentification} for this call, or {@code null} if a
-     * {@link CallScreeningService} did not provide information.
-     */
-    public @Nullable CallIdentification getCallIdentification() {
-        return mCallIdentification;
-    }
-
-    /**
      * Indicates whether the call is an incoming or outgoing call.
      */
     public @CallDirection int getCallDirection() {
@@ -366,7 +354,6 @@
             boolean isRttCallChanged = source.readByte() == 1;
             ParcelableRttCall rttCall = source.readParcelable(classLoader);
             long creationTimeMillis = source.readLong();
-            CallIdentification callIdentification = source.readParcelable(classLoader);
             int callDirection = source.readInt();
             return new ParcelableCall(
                     id,
@@ -395,7 +382,6 @@
                     intentExtras,
                     extras,
                     creationTimeMillis,
-                    callIdentification,
                     callDirection);
         }
 
@@ -441,7 +427,6 @@
         destination.writeByte((byte) (mIsRttCallChanged ? 1 : 0));
         destination.writeParcelable(mRttCall, 0);
         destination.writeLong(mCreationTimeMillis);
-        destination.writeParcelable(mCallIdentification, 0);
         destination.writeInt(mCallDirection);
     }
 
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 3d0a3c5..391d788 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2002,33 +2002,6 @@
     }
 
     /**
-     * Called by the default dialer to report to Telecom when the user has marked a previous
-     * incoming call as a nuisance call or not.
-     * <p>
-     * Where the user has chosen a {@link CallScreeningService} to fill the call screening role,
-     * Telecom will notify that {@link CallScreeningService} of the user's report.
-     * <p>
-     * Requires that the caller is the default dialer app.
-     *
-     * @param handle The phone number of an incoming call which the user is reporting as either a
-     *               nuisance of non-nuisance call.
-     * @param isNuisanceCall {@code true} if the user is reporting the call as a nuisance call,
-     *                       {@code false} if the user is reporting the call as a non-nuisance call.
-     */
-    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    public void reportNuisanceCallStatus(@NonNull Uri handle, boolean isNuisanceCall) {
-        ITelecomService service = getTelecomService();
-        if (service != null) {
-            try {
-                service.reportNuisanceCallStatus(handle, isNuisanceCall,
-                        mContext.getOpPackageName());
-            } catch (RemoteException e) {
-                Log.e(TAG, "Error calling ITelecomService#showCallScreen", e);
-            }
-        }
-    }
-
-    /**
      * Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity.
      * @param intent The {@link Intent#ACTION_CALL} intent to handle.
      * @hide
diff --git a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
index 1160d27..3ee3285 100644
--- a/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/ICallScreeningAdapter.aidl
@@ -17,7 +17,6 @@
 package com.android.internal.telecom;
 
 import android.content.ComponentName;
-import android.telecom.CallIdentification;
 
 /**
  * Internal remote callback interface for call screening services.
@@ -37,8 +36,4 @@
             boolean shouldAddToCallLog,
             boolean shouldShowNotification,
             in ComponentName componentName);
-
-    void provideCallIdentification(
-            String callId,
-            in CallIdentification callIdentification);
 }
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 93eea56..a814c03 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -286,8 +286,6 @@
      */
     boolean isInEmergencyCall();
 
-    oneway void reportNuisanceCallStatus(in Uri address, boolean isNuisance, String callingPackage);
-
     /**
      * @see TelecomServiceImpl#handleCallIntent
      */
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0b44367e..a567d03 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1896,6 +1896,16 @@
     public static final String KEY_IMSI_ENCODING_METHOD_INT = "imsi_encoding_method_int";
 
     /**
+     * Defines the sequence of sending an encrypted IMSI identity for EAP-SIM/AKA authentication.
+     * The value set as below:
+     * 1 - encrypted IMSI as EAP-RESPONSE/IDENTITY (default one).
+     * 2 - anonymous as EAP-RESPONSE/IDENTITY -> encrypted IMSI as EAP-RESPONSE/AKA|SIM-IDENTITY.
+     *
+     * @hide
+     */
+    public static final String KEY_EAP_IDENTITY_SEQUENCE_INT = "imsi_eap_identity_sequence_int";
+
+    /**
      * Time delay (in ms) after which we show the notification to switch the preferred
      * network.
      * @hide
@@ -2790,7 +2800,7 @@
         sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL, false);
-        sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, true);
+        sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL, false);
         sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
         sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true);
@@ -3054,6 +3064,7 @@
         sDefaults.putBoolean(KEY_ALLOW_METERED_NETWORK_FOR_CERT_DOWNLOAD_BOOL, false);
         sDefaults.putStringArray(KEY_CARRIER_WIFI_STRING_ARRAY, null);
         sDefaults.putInt(KEY_IMSI_ENCODING_METHOD_INT, 2045);
+        sDefaults.putInt(KEY_EAP_IDENTITY_SEQUENCE_INT, 1);
         sDefaults.putInt(KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, -1);
         sDefaults.putInt(KEY_EMERGENCY_NOTIFICATION_DELAY_INT, -1);
         sDefaults.putBoolean(KEY_ALLOW_USSD_REQUESTS_VIA_TELEPHONY_MANAGER_BOOL, true);
diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
index 465c2b1..2cb369d 100644
--- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
+++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java
@@ -78,7 +78,7 @@
      *
      * @hide
      */
-    public final boolean isUsingCarrierAggregation;
+    public boolean mIsUsingCarrierAggregation;
 
     /**
      * @hide
@@ -92,7 +92,7 @@
         this.isNrAvailable = isNrAvailable;
         this.isEnDcAvailable = isEnDcAvailable;
         this.mLteVopsSupportInfo = lteVops;
-        this.isUsingCarrierAggregation = isUsingCarrierAggregation;
+        this.mIsUsingCarrierAggregation = isUsingCarrierAggregation;
     }
 
     private DataSpecificRegistrationInfo(Parcel source) {
@@ -101,7 +101,7 @@
         isNrAvailable = source.readBoolean();
         isEnDcAvailable = source.readBoolean();
         mLteVopsSupportInfo = LteVopsSupportInfo.CREATOR.createFromParcel(source);
-        isUsingCarrierAggregation = source.readBoolean();
+        mIsUsingCarrierAggregation = source.readBoolean();
     }
 
     @Override
@@ -111,7 +111,7 @@
         dest.writeBoolean(isNrAvailable);
         dest.writeBoolean(isEnDcAvailable);
         mLteVopsSupportInfo.writeToParcel(dest, flags);
-        dest.writeBoolean(isUsingCarrierAggregation);
+        dest.writeBoolean(mIsUsingCarrierAggregation);
     }
 
     @Override
@@ -128,7 +128,7 @@
                 .append(" isNrAvailable = " + isNrAvailable)
                 .append(" isEnDcAvailable = " + isEnDcAvailable)
                 .append(" " + mLteVopsSupportInfo.toString())
-                .append(" isUsingCarrierAggregation = " + isUsingCarrierAggregation)
+                .append(" mIsUsingCarrierAggregation = " + mIsUsingCarrierAggregation)
                 .append(" }")
                 .toString();
     }
@@ -136,7 +136,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable, isEnDcAvailable,
-                mLteVopsSupportInfo, isUsingCarrierAggregation);
+                mLteVopsSupportInfo, mIsUsingCarrierAggregation);
     }
 
     @Override
@@ -151,7 +151,7 @@
                 && this.isNrAvailable == other.isNrAvailable
                 && this.isEnDcAvailable == other.isEnDcAvailable
                 && this.mLteVopsSupportInfo.equals(other.mLteVopsSupportInfo)
-                && this.isUsingCarrierAggregation == other.isUsingCarrierAggregation;
+                && this.mIsUsingCarrierAggregation == other.mIsUsingCarrierAggregation;
     }
 
     public static final @NonNull Parcelable.Creator<DataSpecificRegistrationInfo> CREATOR =
@@ -174,4 +174,22 @@
     public LteVopsSupportInfo getLteVopsSupportInfo() {
         return mLteVopsSupportInfo;
     }
+
+    /**
+     * Set the flag indicating if using carrier aggregation.
+     *
+     * @param isUsingCarrierAggregation {@code true} if using carrier aggregation.
+     * @hide
+     */
+    public void setIsUsingCarrierAggregation(boolean isUsingCarrierAggregation) {
+        mIsUsingCarrierAggregation = isUsingCarrierAggregation;
+    }
+
+    /**
+     * @return {@code true} if using carrier aggregation.
+     * @hide
+     */
+    public boolean isUsingCarrierAggregation() {
+        return mIsUsingCarrierAggregation;
+    }
 }
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 2bb02e7..7b9f6d5 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -368,6 +368,13 @@
      * @hide
      */
     public void setAccessNetworkTechnology(@NetworkType int tech) {
+        if (tech == TelephonyManager.NETWORK_TYPE_LTE_CA) {
+            // For old device backward compatibility support
+            tech = TelephonyManager.NETWORK_TYPE_LTE;
+            if (mDataSpecificInfo != null) {
+                mDataSpecificInfo.setIsUsingCarrierAggregation(true);
+            }
+        }
         mAccessNetworkTechnology = tech;
     }
 
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 549c044..b75e515 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -2016,7 +2016,16 @@
     private static boolean isEmergencyNumberInternal(int subId, String number,
                                                      String defaultCountryIso,
                                                      boolean useExactMatch) {
-        return TelephonyManager.getDefault().isEmergencyNumber(number);
+        try {
+            if (useExactMatch) {
+                return TelephonyManager.getDefault().isEmergencyNumber(number);
+            } else {
+                return TelephonyManager.getDefault().isPotentialEmergencyNumber(number);
+            }
+        } catch (RuntimeException ex) {
+            Rlog.e(LOG_TAG, "isEmergencyNumberInternal: RuntimeException: " + ex);
+        }
+        return false;
     }
 
     /**
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index a794ba1..d2c0705 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -312,18 +312,6 @@
     private boolean mIsManualNetworkSelection;
 
     private boolean mIsEmergencyOnly;
-    /**
-     * TODO: remove mRilVoiceRadioTechnology after completely migrate to
-     * {@link TelephonyManager.NetworkType}
-     */
-    @RilRadioTechnology
-    private int mRilVoiceRadioTechnology;
-    /**
-     * TODO: remove mRilDataRadioTechnology after completely migrate to
-     * {@link TelephonyManager.NetworkType}
-     */
-    @RilRadioTechnology
-    private int mRilDataRadioTechnology;
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private boolean mCssIndicator;
@@ -340,9 +328,6 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private int mCdmaEriIconMode;
 
-    @UnsupportedAppUsage
-    private boolean mIsUsingCarrierAggregation;
-
     @FrequencyRange
     private int mNrFrequencyRange;
     private int mChannelNumber;
@@ -420,8 +405,6 @@
         mDataOperatorAlphaShort = s.mDataOperatorAlphaShort;
         mDataOperatorNumeric = s.mDataOperatorNumeric;
         mIsManualNetworkSelection = s.mIsManualNetworkSelection;
-        mRilVoiceRadioTechnology = s.mRilVoiceRadioTechnology;
-        mRilDataRadioTechnology = s.mRilDataRadioTechnology;
         mCssIndicator = s.mCssIndicator;
         mNetworkId = s.mNetworkId;
         mSystemId = s.mSystemId;
@@ -430,7 +413,6 @@
         mCdmaEriIconIndex = s.mCdmaEriIconIndex;
         mCdmaEriIconMode = s.mCdmaEriIconMode;
         mIsEmergencyOnly = s.mIsEmergencyOnly;
-        mIsUsingCarrierAggregation = s.mIsUsingCarrierAggregation;
         mChannelNumber = s.mChannelNumber;
         mCellBandwidths = s.mCellBandwidths == null ? null :
                 Arrays.copyOf(s.mCellBandwidths, s.mCellBandwidths.length);
@@ -457,8 +439,6 @@
         mDataOperatorAlphaShort = in.readString();
         mDataOperatorNumeric = in.readString();
         mIsManualNetworkSelection = in.readInt() != 0;
-        mRilVoiceRadioTechnology = in.readInt();
-        mRilDataRadioTechnology = in.readInt();
         mCssIndicator = (in.readInt() != 0);
         mNetworkId = in.readInt();
         mSystemId = in.readInt();
@@ -467,7 +447,6 @@
         mCdmaEriIconIndex = in.readInt();
         mCdmaEriIconMode = in.readInt();
         mIsEmergencyOnly = in.readInt() != 0;
-        mIsUsingCarrierAggregation = in.readInt() != 0;
         mLteEarfcnRsrpBoost = in.readInt();
         mNetworkRegistrationInfos = new ArrayList<>();
         in.readList(mNetworkRegistrationInfos, NetworkRegistrationInfo.class.getClassLoader());
@@ -486,8 +465,6 @@
         out.writeString(mDataOperatorAlphaShort);
         out.writeString(mDataOperatorNumeric);
         out.writeInt(mIsManualNetworkSelection ? 1 : 0);
-        out.writeInt(mRilVoiceRadioTechnology);
-        out.writeInt(mRilDataRadioTechnology);
         out.writeInt(mCssIndicator ? 1 : 0);
         out.writeInt(mNetworkId);
         out.writeInt(mSystemId);
@@ -496,7 +473,6 @@
         out.writeInt(mCdmaEriIconIndex);
         out.writeInt(mCdmaEriIconMode);
         out.writeInt(mIsEmergencyOnly ? 1 : 0);
-        out.writeInt(mIsUsingCarrierAggregation ? 1 : 0);
         out.writeInt(mLteEarfcnRsrpBoost);
         out.writeList(mNetworkRegistrationInfos);
         out.writeInt(mChannelNumber);
@@ -568,7 +544,7 @@
     @DuplexMode
     public int getDuplexMode() {
         // only support LTE duplex mode
-        if (!isLte(mRilDataRadioTechnology)) {
+        if (!isLte(getRilDataRadioTechnology())) {
             return DUPLEX_MODE_UNKNOWN;
         }
 
@@ -850,8 +826,6 @@
                 mDataOperatorAlphaShort,
                 mDataOperatorNumeric,
                 mIsManualNetworkSelection,
-                mRilVoiceRadioTechnology,
-                mRilDataRadioTechnology,
                 mCssIndicator,
                 mNetworkId,
                 mSystemId,
@@ -860,7 +834,6 @@
                 mCdmaEriIconIndex,
                 mCdmaEriIconMode,
                 mIsEmergencyOnly,
-                mIsUsingCarrierAggregation,
                 mLteEarfcnRsrpBoost,
                 mNetworkRegistrationInfos,
                 mNrFrequencyRange);
@@ -871,7 +844,7 @@
         if (!(o instanceof ServiceState)) return false;
         ServiceState s = (ServiceState) o;
 
-        return (mVoiceRegState == s.mVoiceRegState
+        return mVoiceRegState == s.mVoiceRegState
                 && mDataRegState == s.mDataRegState
                 && mIsManualNetworkSelection == s.mIsManualNetworkSelection
                 && mChannelNumber == s.mChannelNumber
@@ -882,8 +855,6 @@
                 && equalsHandlesNulls(mDataOperatorAlphaLong, s.mDataOperatorAlphaLong)
                 && equalsHandlesNulls(mDataOperatorAlphaShort, s.mDataOperatorAlphaShort)
                 && equalsHandlesNulls(mDataOperatorNumeric, s.mDataOperatorNumeric)
-                && equalsHandlesNulls(mRilVoiceRadioTechnology, s.mRilVoiceRadioTechnology)
-                && equalsHandlesNulls(mRilDataRadioTechnology, s.mRilDataRadioTechnology)
                 && equalsHandlesNulls(mCssIndicator, s.mCssIndicator)
                 && equalsHandlesNulls(mNetworkId, s.mNetworkId)
                 && equalsHandlesNulls(mSystemId, s.mSystemId)
@@ -891,7 +862,6 @@
                 && equalsHandlesNulls(mCdmaDefaultRoamingIndicator,
                         s.mCdmaDefaultRoamingIndicator)
                 && mIsEmergencyOnly == s.mIsEmergencyOnly
-                && mIsUsingCarrierAggregation == s.mIsUsingCarrierAggregation)
                 && (mNetworkRegistrationInfos == null
                 ? s.mNetworkRegistrationInfos == null : s.mNetworkRegistrationInfos != null
                 && mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos))
@@ -1035,27 +1005,27 @@
             .append(", mDataOperatorAlphaShort=").append(mDataOperatorAlphaShort)
             .append(", isManualNetworkSelection=").append(mIsManualNetworkSelection)
             .append(mIsManualNetworkSelection ? "(manual)" : "(automatic)")
-            .append(", mRilVoiceRadioTechnology=").append(mRilVoiceRadioTechnology)
-            .append("(" + rilRadioTechnologyToString(mRilVoiceRadioTechnology) + ")")
-            .append(", mRilDataRadioTechnology=").append(mRilDataRadioTechnology)
-            .append("(" + rilRadioTechnologyToString(mRilDataRadioTechnology) + ")")
+            .append(", getRilVoiceRadioTechnology=").append(getRilVoiceRadioTechnology())
+            .append("(" + rilRadioTechnologyToString(getRilVoiceRadioTechnology()) + ")")
+            .append(", getRilDataRadioTechnology=").append(getRilDataRadioTechnology())
+            .append("(" + rilRadioTechnologyToString(getRilDataRadioTechnology()) + ")")
             .append(", mCssIndicator=").append(mCssIndicator ? "supported" : "unsupported")
             .append(", mNetworkId=").append(mNetworkId)
             .append(", mSystemId=").append(mSystemId)
             .append(", mCdmaRoamingIndicator=").append(mCdmaRoamingIndicator)
             .append(", mCdmaDefaultRoamingIndicator=").append(mCdmaDefaultRoamingIndicator)
             .append(", mIsEmergencyOnly=").append(mIsEmergencyOnly)
-            .append(", mIsUsingCarrierAggregation=").append(mIsUsingCarrierAggregation)
+            .append(", isUsingCarrierAggregation=").append(isUsingCarrierAggregation())
             .append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost)
             .append(", mNetworkRegistrationInfos=").append(mNetworkRegistrationInfos)
             .append(", mNrFrequencyRange=").append(mNrFrequencyRange)
             .append("}").toString();
     }
 
-    private void setNullState(int state) {
-        if (DBG) Rlog.d(LOG_TAG, "[ServiceState] setNullState=" + state);
-        mVoiceRegState = state;
-        mDataRegState = state;
+    private void init() {
+        if (DBG) Rlog.d(LOG_TAG, "init");
+        mVoiceRegState = STATE_OUT_OF_SERVICE;
+        mDataRegState = STATE_OUT_OF_SERVICE;
         mChannelNumber = -1;
         mCellBandwidths = new int[0];
         mVoiceOperatorAlphaLong = null;
@@ -1065,8 +1035,6 @@
         mDataOperatorAlphaShort = null;
         mDataOperatorNumeric = null;
         mIsManualNetworkSelection = false;
-        mRilVoiceRadioTechnology = 0;
-        mRilDataRadioTechnology = 0;
         mCssIndicator = false;
         mNetworkId = -1;
         mSystemId = -1;
@@ -1075,18 +1043,28 @@
         mCdmaEriIconIndex = -1;
         mCdmaEriIconMode = -1;
         mIsEmergencyOnly = false;
-        mIsUsingCarrierAggregation = false;
         mLteEarfcnRsrpBoost = 0;
-        mNetworkRegistrationInfos = new ArrayList<>();
         mNrFrequencyRange = FREQUENCY_RANGE_UNKNOWN;
+        addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN)
+                .build());
+        addNetworkRegistrationInfo(new NetworkRegistrationInfo.Builder()
+                .setDomain(NetworkRegistrationInfo.DOMAIN_PS)
+                .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+                .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN)
+                .build());
     }
 
     public void setStateOutOfService() {
-        setNullState(STATE_OUT_OF_SERVICE);
+        init();
     }
 
     public void setStateOff() {
-        setNullState(STATE_POWER_OFF);
+        init();
+        mVoiceRegState = STATE_POWER_OFF;
+        mDataRegState = STATE_POWER_OFF;
     }
 
     public void setState(int state) {
@@ -1304,8 +1282,8 @@
         m.putString("data-operator-alpha-short", mDataOperatorAlphaShort);
         m.putString("data-operator-numeric", mDataOperatorNumeric);
         m.putBoolean("manual", mIsManualNetworkSelection);
-        m.putInt("radioTechnology", mRilVoiceRadioTechnology);
-        m.putInt("dataRadioTechnology", mRilDataRadioTechnology);
+        m.putInt("radioTechnology", getRilVoiceRadioTechnology());
+        m.putInt("dataRadioTechnology", getRadioTechnology());
         m.putBoolean("cssIndicator", mCssIndicator);
         m.putInt("networkId", mNetworkId);
         m.putInt("systemId", mSystemId);
@@ -1313,7 +1291,7 @@
         m.putInt("cdmaDefaultRoamingIndicator", mCdmaDefaultRoamingIndicator);
         m.putBoolean("emergencyOnly", mIsEmergencyOnly);
         m.putBoolean("isDataRoamingFromRegistration", getDataRoamingFromRegistration());
-        m.putBoolean("isUsingCarrierAggregation", mIsUsingCarrierAggregation);
+        m.putBoolean("isUsingCarrierAggregation", isUsingCarrierAggregation());
         m.putInt("LteEarfcnRsrpBoost", mLteEarfcnRsrpBoost);
         m.putInt("ChannelNumber", mChannelNumber);
         m.putIntArray("CellBandwidths", mCellBandwidths);
@@ -1323,13 +1301,9 @@
     /** @hide */
     @TestApi
     public void setRilVoiceRadioTechnology(@RilRadioTechnology int rt) {
-        if (rt == RIL_RADIO_TECHNOLOGY_LTE_CA) {
-            rt = RIL_RADIO_TECHNOLOGY_LTE;
-        }
-
-        this.mRilVoiceRadioTechnology = rt;
-
-        // sync to network registration state
+        Rlog.e(LOG_TAG, "ServiceState.setRilVoiceRadioTechnology() called. It's encouraged to "
+                + "use addNetworkRegistrationInfo() instead *******");
+        // Sync to network registration state
         NetworkRegistrationInfo regState = getNetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
         if (regState == null) {
@@ -1339,24 +1313,18 @@
                     .build();
             addNetworkRegistrationInfo(regState);
         }
-        regState.setAccessNetworkTechnology(
-                rilRadioTechnologyToNetworkType(mRilVoiceRadioTechnology));
+        regState.setAccessNetworkTechnology(rilRadioTechnologyToNetworkType(rt));
     }
 
+
     /** @hide */
     @TestApi
     public void setRilDataRadioTechnology(@RilRadioTechnology int rt) {
-        if (rt == RIL_RADIO_TECHNOLOGY_LTE_CA) {
-            rt = RIL_RADIO_TECHNOLOGY_LTE;
-            this.mIsUsingCarrierAggregation = true;
-        } else {
-            this.mIsUsingCarrierAggregation = false;
-        }
-        this.mRilDataRadioTechnology = rt;
-        if (VDBG) Rlog.d(LOG_TAG, "[ServiceState] setRilDataRadioTechnology=" +
-                mRilDataRadioTechnology);
-
-        // sync to network registration state
+        Rlog.e(LOG_TAG, "ServiceState.setRilDataRadioTechnology() called. It's encouraged to "
+                + "use addNetworkRegistrationInfo() instead *******");
+        // Sync to network registration state. Always write down the WWAN transport. For AP-assisted
+        // mode device, use addNetworkRegistrationInfo() to set the correct transport if RAT
+        // is IWLAN.
         NetworkRegistrationInfo regState = getNetworkRegistrationInfo(
                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
 
@@ -1367,18 +1335,32 @@
                     .build();
             addNetworkRegistrationInfo(regState);
         }
-        regState.setAccessNetworkTechnology(
-                rilRadioTechnologyToNetworkType(mRilDataRadioTechnology));
+        regState.setAccessNetworkTechnology(rilRadioTechnologyToNetworkType(rt));
     }
 
     /** @hide */
     public boolean isUsingCarrierAggregation() {
-        return mIsUsingCarrierAggregation;
+        NetworkRegistrationInfo nri = getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        if (nri != null) {
+            DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo();
+            if (dsri != null) {
+                return dsri.isUsingCarrierAggregation();
+            }
+        }
+        return false;
     }
 
     /** @hide */
     public void setIsUsingCarrierAggregation(boolean ca) {
-        mIsUsingCarrierAggregation = ca;
+        NetworkRegistrationInfo nri = getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        if (nri != null) {
+            DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo();
+            if (dsri != null) {
+                dsri.setIsUsingCarrierAggregation(ca);
+            }
+        }
     }
 
     /**
@@ -1435,12 +1417,29 @@
     /** @hide */
     @UnsupportedAppUsage
     public int getRilVoiceRadioTechnology() {
-        return this.mRilVoiceRadioTechnology;
+        NetworkRegistrationInfo wwanRegInfo = getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        if (wwanRegInfo != null) {
+            return networkTypeToRilRadioTechnology(wwanRegInfo.getAccessNetworkTechnology());
+        }
+        return RIL_RADIO_TECHNOLOGY_UNKNOWN;
     }
     /** @hide */
     @UnsupportedAppUsage
     public int getRilDataRadioTechnology() {
-        return this.mRilDataRadioTechnology;
+        NetworkRegistrationInfo wwanRegInfo = getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        NetworkRegistrationInfo wlanRegInfo = getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+        if (wlanRegInfo != null
+                && wlanRegInfo.getAccessNetworkTechnology() == TelephonyManager.NETWORK_TYPE_IWLAN
+                && wlanRegInfo.getRegistrationState()
+                == NetworkRegistrationInfo.REGISTRATION_STATE_HOME) {
+            return RIL_RADIO_TECHNOLOGY_IWLAN;
+        } else if (wwanRegInfo != null) {
+            return networkTypeToRilRadioTechnology(wwanRegInfo.getAccessNetworkTechnology());
+        }
+        return RIL_RADIO_TECHNOLOGY_UNKNOWN;
     }
     /**
      * @hide
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f393cba..bdd01e2 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -10861,24 +10861,28 @@
     }
 
     /**
-     * Get whether reboot is required or not after making changes to modem configurations.
+     * Get whether making changes to modem configurations by {@link #switchMultiSimConfig(int)} will
+     * trigger device reboot.
      * The modem configuration change refers to switching from single SIM configuration to DSDS
      * or the other way around.
-     * @Return {@code true} if reboot is required after making changes to modem configurations,
-     * otherwise return {@code false}.
      *
-     * @hide
+     *  <p>Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} or that the
+     * calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+     *
+     * @return {@code true} if reboot will be triggered after making changes to modem
+     * configurations, otherwise return {@code false}.
      */
-    @SystemApi
-    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
-    public boolean isRebootRequiredForModemConfigChange() {
+    @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
+    public boolean doesSwitchMultiSimConfigTriggerReboot() {
         try {
             ITelephony service = getITelephony();
             if (service != null) {
-                return service.isRebootRequiredForModemConfigChange();
+                return service.doesSwitchMultiSimConfigTriggerReboot(getSubId(),
+                        getOpPackageName());
             }
         } catch (RemoteException e) {
-            Log.e(TAG, "isRebootRequiredForModemConfigChange RemoteException", e);
+            Log.e(TAG, "doesSwitchMultiSimConfigTriggerReboot RemoteException", e);
         }
         return false;
     }
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index cde10ea..96a2514 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -485,7 +485,7 @@
     public boolean isEnabled() {
         // In the future, this may reach out to IEuiccController (if non-null) to check any dynamic
         // restrictions.
-        return getIEuiccController() != null;
+        return getIEuiccController() != null && refreshCardIdIfUninitialized();
     }
 
     /**
@@ -499,7 +499,7 @@
      */
     @Nullable
     public String getEid() {
-        if (!refreshCardIdIfUninitialized()) {
+        if (!isEnabled()) {
             return null;
         }
         try {
@@ -522,7 +522,7 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public int getOtaStatus() {
-        if (!refreshCardIdIfUninitialized()) {
+        if (!isEnabled()) {
             return EUICC_OTA_STATUS_UNAVAILABLE;
         }
         try {
@@ -557,7 +557,7 @@
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void downloadSubscription(DownloadableSubscription subscription,
             boolean switchAfterDownload, PendingIntent callbackIntent) {
-        if (!refreshCardIdIfUninitialized()) {
+        if (!isEnabled()) {
             sendUnavailableError(callbackIntent);
             return;
         }
@@ -619,7 +619,7 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void continueOperation(Intent resolutionIntent, Bundle resolutionExtras) {
-        if (!refreshCardIdIfUninitialized()) {
+        if (!isEnabled()) {
             PendingIntent callbackIntent =
                     resolutionIntent.getParcelableExtra(
                             EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT);
@@ -656,7 +656,7 @@
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void getDownloadableSubscriptionMetadata(
             DownloadableSubscription subscription, PendingIntent callbackIntent) {
-        if (!refreshCardIdIfUninitialized()) {
+        if (!isEnabled()) {
             sendUnavailableError(callbackIntent);
             return;
         }
@@ -686,7 +686,7 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void getDefaultDownloadableSubscriptionList(PendingIntent callbackIntent) {
-        if (!refreshCardIdIfUninitialized()) {
+        if (!isEnabled()) {
             sendUnavailableError(callbackIntent);
             return;
         }
@@ -705,7 +705,7 @@
      */
     @Nullable
     public EuiccInfo getEuiccInfo() {
-        if (!refreshCardIdIfUninitialized()) {
+        if (!isEnabled()) {
             return null;
         }
         try {
@@ -730,7 +730,7 @@
      */
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void deleteSubscription(int subscriptionId, PendingIntent callbackIntent) {
-        if (!refreshCardIdIfUninitialized()) {
+        if (!isEnabled()) {
             sendUnavailableError(callbackIntent);
             return;
         }
@@ -770,7 +770,7 @@
      */
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void switchToSubscription(int subscriptionId, PendingIntent callbackIntent) {
-        if (!refreshCardIdIfUninitialized()) {
+        if (!isEnabled()) {
             sendUnavailableError(callbackIntent);
             return;
         }
@@ -796,7 +796,7 @@
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void updateSubscriptionNickname(
             int subscriptionId, @Nullable String nickname, @NonNull PendingIntent callbackIntent) {
-        if (!refreshCardIdIfUninitialized()) {
+        if (!isEnabled()) {
             sendUnavailableError(callbackIntent);
             return;
         }
@@ -820,7 +820,7 @@
     @SystemApi
     @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
     public void eraseSubscriptions(PendingIntent callbackIntent) {
-        if (!refreshCardIdIfUninitialized()) {
+        if (!isEnabled()) {
             sendUnavailableError(callbackIntent);
             return;
         }
@@ -850,7 +850,7 @@
      * @hide
      */
     public void retainSubscriptionsForFactoryReset(PendingIntent callbackIntent) {
-        if (!refreshCardIdIfUninitialized()) {
+        if (!isEnabled()) {
             sendUnavailableError(callbackIntent);
             return;
         }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 8332ffe..c8cd249 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1945,10 +1945,10 @@
     void switchMultiSimConfig(int numOfSims);
 
     /**
-     * Get if reboot is required upon altering modems configurations
+     * Get if altering modems configurations will trigger reboot.
      * @hide
      */
-    boolean isRebootRequiredForModemConfigChange();
+    boolean doesSwitchMultiSimConfigTriggerReboot(int subId, String callingPackage);
 
     /**
      * Get the mapping from logical slots to physical slots.
diff --git a/tests/benchmarks/Android.bp b/tests/benchmarks/Android.bp
new file mode 100644
index 0000000..f16ddb9
--- /dev/null
+++ b/tests/benchmarks/Android.bp
@@ -0,0 +1,29 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// build framework base core benchmarks
+// ============================================================
+
+java_library {
+    name: "networkStatsFactory-benchmarks",
+    installable: true,
+
+    srcs: ["src/**/*.java"],
+
+    libs: [
+        "caliper-api-target",
+        "services.core",
+    ],
+
+}
diff --git a/core/tests/benchmarks/src/com/android/internal/net/NetworkStatsFactoryBenchmark.java b/tests/benchmarks/src/com/android/server/net/NetworkStatsFactoryBenchmark.java
similarity index 95%
rename from core/tests/benchmarks/src/com/android/internal/net/NetworkStatsFactoryBenchmark.java
rename to tests/benchmarks/src/com/android/server/net/NetworkStatsFactoryBenchmark.java
index c213464..ef014f0 100644
--- a/core/tests/benchmarks/src/com/android/internal/net/NetworkStatsFactoryBenchmark.java
+++ b/tests/benchmarks/src/com/android/server/net/NetworkStatsFactoryBenchmark.java
@@ -14,10 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.internal.net;
+package com.android.server.net;
 
 import android.net.NetworkStats;
 import android.os.SystemClock;
+import com.android.server.net.NetworkStatsFactory;
 import com.google.caliper.AfterExperiment;
 import com.google.caliper.BeforeExperiment;
 import java.io.File;
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
index c62d85e..70b4089 100644
--- a/tests/net/Android.bp
+++ b/tests/net/Android.bp
@@ -1,11 +1,8 @@
 //########################################################################
 // Build FrameworksNetTests package
 //########################################################################
-
-android_test {
-    name: "FrameworksNetTests",
-    // Include all test java files.
-    srcs: ["java/**/*.java"],
+java_defaults {
+    name: "FrameworksNetTests-jni-defaults",
     static_libs: [
         "frameworks-base-testutils",
         "framework-protos",
@@ -20,6 +17,53 @@
         "android.test.base",
         "android.test.mock",
     ],
+    jni_libs: [
+        "ld-android",
+        "libartbase",
+        "libbacktrace",
+        "libbase",
+        "libbinder",
+        "libbinderthreadstate",
+        "libbpf",
+        "libbpf_android",
+        "libc++",
+        "libcgrouprc",
+        "libcrypto",
+        "libcutils",
+        "libdexfile",
+        "libdl_android",
+        "libhidl-gen-utils",
+        "libhidlbase",
+        "libhidltransport",
+        "libhwbinder",
+        "libjsoncpp",
+        "liblog",
+        "liblzma",
+        "libnativehelper",
+        "libnetdbpf",
+        "libnetdutils",
+        "libpackagelistparser",
+        "libpcre2",
+        "libprocessgroup",
+        "libselinux",
+        "libui",
+        "libutils",
+        "libvintf",
+        "libvndksupport",
+        "libtinyxml2",
+        "libunwindstack",
+        "libutilscallstack",
+        "libziparchive",
+        "libz",
+        "netd_aidl_interface-cpp",
+        "libnetworkstatsfactorytestjni",
+    ],
+}
+
+android_test {
+    name: "FrameworksNetTests",
+    defaults: ["FrameworksNetTests-jni-defaults"],
+    srcs: ["java/**/*.java"],
     platform_apis: true,
     test_suites: ["device-tests"],
     certificate: "platform",
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index 354c08f..106cd1f 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -20,10 +20,13 @@
 import static android.Manifest.permission.CHANGE_WIFI_STATE;
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.INTERNET;
 import static android.Manifest.permission.NETWORK_STACK;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_OEM;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_PRODUCT;
 import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_VENDOR;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
 import static android.os.Process.SYSTEM_UID;
 
@@ -41,26 +44,35 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageList;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.net.INetd;
 import android.os.Build;
 import android.os.INetworkManagementService;
 import android.os.UserHandle;
+import android.util.SparseIntArray;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.LocalServices;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 
 @RunWith(AndroidJUnit4.class)
@@ -69,7 +81,11 @@
     private static final int MOCK_USER1 = 0;
     private static final int MOCK_USER2 = 1;
     private static final int MOCK_UID1 = 10001;
+    private static final int MOCK_UID2 = 10086;
+    private static final int SYSTEM_UID1 = 1000;
+    private static final int SYSTEM_UID2 = 1008;
     private static final String MOCK_PACKAGE1 = "appName1";
+    private static final String MOCK_PACKAGE2 = "appName2";
     private static final String SYSTEM_PACKAGE1 = "sysName1";
     private static final String SYSTEM_PACKAGE2 = "sysName2";
     private static final String PARTITION_SYSTEM = "system";
@@ -82,14 +98,29 @@
     @Mock private Context mContext;
     @Mock private PackageManager mPackageManager;
     @Mock private INetworkManagementService mNMS;
+    @Mock private INetd mNetdService;
+    @Mock private PackageManagerInternal mMockPmi;
 
+    private PackageManagerInternal.PackageListObserver mObserver;
     private PermissionMonitor mPermissionMonitor;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
-        mPermissionMonitor = spy(new PermissionMonitor(mContext, mNMS));
+        mPermissionMonitor = spy(new PermissionMonitor(mContext, mNMS, mNetdService));
+
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mMockPmi);
+        when(mMockPmi.getPackageList(any())).thenReturn(new PackageList(new ArrayList<String>(),
+                  /* observer */ null));
+        when(mPackageManager.getInstalledPackages(anyInt())).thenReturn(/* empty app list */ null);
+        mPermissionMonitor.startMonitoring();
+
+        final ArgumentCaptor<PackageManagerInternal.PackageListObserver> observerCaptor =
+                ArgumentCaptor.forClass(PackageManagerInternal.PackageListObserver.class);
+        verify(mMockPmi).getPackageList(observerCaptor.capture());
+        mObserver = observerCaptor.getValue();
     }
 
     private boolean hasBgPermission(String partition, int targetSdkVersion, int uid,
@@ -104,9 +135,20 @@
     }
 
     private PackageInfo packageInfoWithPermissions(String[] permissions, String partition) {
+        int[] requestedPermissionsFlags = new int[permissions.length];
+        for (int i = 0; i < permissions.length; i++) {
+            requestedPermissionsFlags[i] = REQUESTED_PERMISSION_GRANTED;
+        }
+        return packageInfoWithPermissions(permissions, partition,
+                requestedPermissionsFlags);
+    }
+
+    private PackageInfo packageInfoWithPermissions(String[] permissions, String partition,
+            int[] requestedPermissionsFlags) {
         final PackageInfo packageInfo = new PackageInfo();
         packageInfo.requestedPermissions = permissions;
         packageInfo.applicationInfo = new ApplicationInfo();
+        packageInfo.requestedPermissionsFlags = requestedPermissionsFlags;
         int privateFlags = 0;
         switch (partition) {
             case PARTITION_OEM:
@@ -337,4 +379,164 @@
             mPermissionMonitor.onPackageRemoved(UserHandle.getUid(user, uid));
         }
     }
+
+    private class NetdServiceMonitor {
+        private final HashMap<Integer, Integer> mPermissions = new HashMap<>();
+
+        NetdServiceMonitor(INetd mockNetdService) throws Exception {
+            // Add hook to verify and track result of setPermission.
+            doAnswer((InvocationOnMock invocation) -> {
+                final Object[] args = invocation.getArguments();
+                final int permission = (int) args[0];
+                for (final int uid : (int[]) args[1]) {
+                    mPermissions.put(uid, permission);
+                }
+                return null;
+            }).when(mockNetdService).trafficSetNetPermForUids(anyInt(), any(int[].class));
+        }
+
+        public void expectPermission(int permission, int[] apps) {
+            for (final int app : apps) {
+                if (!mPermissions.containsKey(app)) {
+                    fail("uid " + app + " does not exist.");
+                }
+                if (mPermissions.get(app) != permission) {
+                    fail("uid " + app + " has wrong permission: " + mPermissions.get(app));
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testPackagePermissionUpdate() throws Exception {
+        final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+        // MOCK_UID1: MOCK_PACKAGE1 only has internet permission.
+        // MOCK_UID2: MOCK_PACKAGE2 does not have any permission.
+        // SYSTEM_UID1: SYSTEM_PACKAGE1 has internet permission and update device stats permission.
+        // SYSTEM_UID2: SYSTEM_PACKAGE2 has only update device stats permission.
+
+        SparseIntArray netdPermissionsAppIds = new SparseIntArray();
+        netdPermissionsAppIds.put(MOCK_UID1, INetd.PERMISSION_INTERNET);
+        netdPermissionsAppIds.put(MOCK_UID2, INetd.NO_PERMISSIONS);
+        netdPermissionsAppIds.put(SYSTEM_UID1, INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS);
+        netdPermissionsAppIds.put(SYSTEM_UID2, INetd.PERMISSION_UPDATE_DEVICE_STATS);
+
+        // Send the permission information to netd, expect permission updated.
+        mPermissionMonitor.sendPackagePermissionsToNetd(netdPermissionsAppIds);
+
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET,
+                new int[]{MOCK_UID1});
+        mNetdServiceMonitor.expectPermission(INetd.NO_PERMISSIONS, new int[]{MOCK_UID2});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{SYSTEM_UID1});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UPDATE_DEVICE_STATS,
+                new int[]{SYSTEM_UID2});
+
+        // Update permission of MOCK_UID1, expect new permission show up.
+        mPermissionMonitor.sendPackagePermissionsForUid(MOCK_UID1,
+                INetd.PERMISSION_INTERNET | INetd.PERMISSION_UPDATE_DEVICE_STATS);
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+
+        // Change permissions of SYSTEM_UID2, expect new permission show up and old permission
+        // revoked.
+        mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID2,
+                INetd.PERMISSION_INTERNET);
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{SYSTEM_UID2});
+
+        // Revoke permission from SYSTEM_UID1, expect no permission stored.
+        mPermissionMonitor.sendPackagePermissionsForUid(SYSTEM_UID1, INetd.NO_PERMISSIONS);
+        mNetdServiceMonitor.expectPermission(INetd.NO_PERMISSIONS, new int[]{SYSTEM_UID1});
+    }
+
+    private PackageInfo addPackage(String packageName, int uid, String[] permissions)
+            throws Exception {
+        PackageInfo packageInfo = packageInfoWithPermissions(permissions, PARTITION_SYSTEM);
+        when(mPackageManager.getPackageInfo(eq(packageName), anyInt())).thenReturn(packageInfo);
+        when(mPackageManager.getPackagesForUid(eq(uid))).thenReturn(new String[]{packageName});
+        mObserver.onPackageAdded(packageName, uid);
+        return packageInfo;
+    }
+
+    @Test
+    public void testPackageInstall() throws Exception {
+        final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+
+        addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+
+        addPackage(MOCK_PACKAGE2, MOCK_UID2, new String[] {INTERNET});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID2});
+    }
+
+    @Test
+    public void testPackageInstallSharedUid() throws Exception {
+        final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+
+        PackageInfo packageInfo1 = addPackage(MOCK_PACKAGE1, MOCK_UID1,
+                new String[] {INTERNET, UPDATE_DEVICE_STATS});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+
+        // Install another package with the same uid and no permissions should not cause the UID to
+        // lose permissions.
+        PackageInfo packageInfo2 = packageInfoWithPermissions(new String[]{}, PARTITION_SYSTEM);
+        when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2);
+        when(mPackageManager.getPackagesForUid(MOCK_UID1))
+              .thenReturn(new String[]{MOCK_PACKAGE1, MOCK_PACKAGE2});
+        mObserver.onPackageAdded(MOCK_PACKAGE2, MOCK_UID1);
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+    }
+
+    @Test
+    public void testPackageUninstallBasic() throws Exception {
+        final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+
+        addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+
+        when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{});
+        mObserver.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
+    }
+
+    @Test
+    public void testPackageUpdate() throws Exception {
+        final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+
+        addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+
+        // Remove and install the same package to simulate the update action
+        when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{});
+        mObserver.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[]{MOCK_UID1});
+
+        addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
+    }
+
+    @Test
+    public void testPackageUninstallWithMultiplePackages() throws Exception {
+        final NetdServiceMonitor mNetdServiceMonitor = new NetdServiceMonitor(mNetdService);
+
+        addPackage(MOCK_PACKAGE1, MOCK_UID1, new String[] {INTERNET, UPDATE_DEVICE_STATS});
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET
+                | INetd.PERMISSION_UPDATE_DEVICE_STATS, new int[]{MOCK_UID1});
+
+        // Mock another package with the same uid but different permissions.
+        PackageInfo packageInfo2 = packageInfoWithPermissions(new String[] {INTERNET},
+                PARTITION_SYSTEM);
+        when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2);
+        when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{
+                MOCK_PACKAGE2});
+
+        mObserver.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1);
+        mNetdServiceMonitor.expectPermission(INetd.PERMISSION_INTERNET, new int[]{MOCK_UID1});
+    }
 }
diff --git a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
index 36a1b7c..2140322 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -122,7 +122,7 @@
         mMockContext = new MockContext(mContext);
     }
 
-    private TetheringConfiguration getTetheringConfiguration(int[] legacyTetherUpstreamTypes) {
+    private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) {
         when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
                 legacyTetherUpstreamTypes);
         return new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
@@ -143,13 +143,13 @@
     public void testDunFromTelephonyManagerMeansDun() {
         when(mTelephonyManager.getTetherApnRequired()).thenReturn(true);
 
-        final TetheringConfiguration cfgWifi = getTetheringConfiguration(new int[]{TYPE_WIFI});
+        final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI);
         final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration(
-                new int[]{TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI});
+                TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI);
         final TetheringConfiguration cfgWifiDun = getTetheringConfiguration(
-                new int[]{TYPE_WIFI, TYPE_MOBILE_DUN});
+                TYPE_WIFI, TYPE_MOBILE_DUN);
         final TetheringConfiguration cfgMobileWifiHipriDun = getTetheringConfiguration(
-                new int[]{TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI, TYPE_MOBILE_DUN});
+                TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI, TYPE_MOBILE_DUN);
 
         for (TetheringConfiguration cfg : Arrays.asList(cfgWifi, cfgMobileWifiHipri,
                 cfgWifiDun, cfgMobileWifiHipriDun)) {
@@ -167,20 +167,20 @@
     public void testDunNotRequiredFromTelephonyManagerMeansNoDun() {
         when(mTelephonyManager.getTetherApnRequired()).thenReturn(false);
 
-        final TetheringConfiguration cfgWifi = getTetheringConfiguration(new int[]{TYPE_WIFI});
+        final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI);
         final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration(
-                new int[]{TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI});
+                TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI);
         final TetheringConfiguration cfgWifiDun = getTetheringConfiguration(
-                new int[]{TYPE_WIFI, TYPE_MOBILE_DUN});
+                TYPE_WIFI, TYPE_MOBILE_DUN);
         final TetheringConfiguration cfgWifiMobile = getTetheringConfiguration(
-                new int[]{TYPE_WIFI, TYPE_MOBILE});
+                TYPE_WIFI, TYPE_MOBILE);
         final TetheringConfiguration cfgWifiHipri = getTetheringConfiguration(
-                new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI});
+                TYPE_WIFI, TYPE_MOBILE_HIPRI);
         final TetheringConfiguration cfgMobileWifiHipriDun = getTetheringConfiguration(
-                new int[]{TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI, TYPE_MOBILE_DUN});
+                TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI, TYPE_MOBILE_DUN);
 
         String msg;
-        // TYPE_MOBILE_DUN should not be present in all of the combinations.
+        // TYPE_MOBILE_DUN should be present in none of the combinations.
         // TYPE_WIFI should not be affected.
         for (TetheringConfiguration cfg : Arrays.asList(cfgWifi, cfgMobileWifiHipri, cfgWifiDun,
                 cfgWifiMobile, cfgWifiHipri, cfgMobileWifiHipriDun)) {
diff --git a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
similarity index 96%
rename from tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
rename to tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
index 4ec4fdd..95bc7d9 100644
--- a/tests/net/java/com/android/internal/net/NetworkStatsFactoryTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsFactoryTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.net;
+package com.android.server.net;
 
 import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
 import static android.net.NetworkStats.METERED_NO;
@@ -70,6 +70,10 @@
             IoUtils.deleteContents(mTestProc);
         }
 
+        // The libandroid_servers which have the native method is not available to
+        // applications. So in order to have a test support native library, the native code
+        // related to networkStatsFactory is compiled to a minimal native library and loaded here.
+        System.loadLibrary("networkstatsfactorytestjni");
         mFactory = new NetworkStatsFactory(mTestProc, false);
     }
 
diff --git a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
index fb84611..a83faf3 100644
--- a/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
+++ b/tests/net/java/com/android/server/net/ipmemorystore/NetworkAttributesTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.net.ipmemorystore;
+package com.android.server.connectivity.ipmemorystore;
 
 import static org.junit.Assert.assertEquals;
 
diff --git a/tests/net/jni/Android.bp b/tests/net/jni/Android.bp
new file mode 100644
index 0000000..9225ffb
--- /dev/null
+++ b/tests/net/jni/Android.bp
@@ -0,0 +1,23 @@
+cc_library_shared {
+    name: "libnetworkstatsfactorytestjni",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+        "-Wthread-safety",
+    ],
+
+    srcs: [
+        ":lib_networkStatsFactory_native",
+        "test_onload.cpp",
+    ],
+
+    shared_libs: [
+        "libbpf_android",
+        "liblog",
+        "libnativehelper",
+        "libnetdbpf",
+        "libnetdutils",
+    ],
+}
diff --git a/tests/net/jni/test_onload.cpp b/tests/net/jni/test_onload.cpp
new file mode 100644
index 0000000..5194ddb
--- /dev/null
+++ b/tests/net/jni/test_onload.cpp
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+/*
+ * this is a mini native libaray for NetworkStatsFactoryTest to run properly. It
+ * load all the native method related to NetworkStatsFactory when test run
+ */
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+namespace android {
+int register_android_server_net_NetworkStatsFactory(JNIEnv* env);
+};
+
+using namespace android;
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
+{
+    JNIEnv* env = NULL;
+    jint result = -1;
+
+    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+        ALOGE("GetEnv failed!");
+        return result;
+    }
+    ALOG_ASSERT(env, "Could not retrieve the env!");
+    register_android_server_net_NetworkStatsFactory(env);
+    return JNI_VERSION_1_4;
+}
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index f576745..4262017 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -374,12 +374,11 @@
         }
 
         /**
-         * Create a network suggestion object use in
+         * Create a network suggestion object for use in
          * {@link WifiManager#addNetworkSuggestions(List)}.
          *
-         * See {@link WifiNetworkSuggestion}.
-         *<p>
-         * Note: Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID
+         *<p class="note">
+         * <b>Note:</b> Apps can set a combination of SSID using {@link #setSsid(String)} and BSSID
          * using {@link #setBssid(MacAddress)} to provide more fine grained network suggestions to
          * the platform.
          * </p>
@@ -387,7 +386,8 @@
          * For example:
          * To provide credentials for one open, one WPA2 and one WPA3 network with their
          * corresponding SSID's:
-         * {@code
+         *
+         * <pre>{@code
          * final WifiNetworkSuggestion suggestion1 =
          *      new Builder()
          *      .setSsid("test111111")
@@ -403,19 +403,20 @@
          *      .setWpa3Passphrase("test6789")
          *      .build()
          * final List<WifiNetworkSuggestion> suggestionsList =
-         *      new ArrayList<WifiNetworkSuggestion> &#123;{
+         *      new ArrayList<WifiNetworkSuggestion> { {
          *          add(suggestion1);
          *          add(suggestion2);
          *          add(suggestion3);
-         *      }};
+         *      } };
          * final WifiManager wifiManager =
          *      context.getSystemService(Context.WIFI_SERVICE);
          * wifiManager.addNetworkSuggestions(suggestionsList);
-         * ...
-         * }
+         * // ...
+         * }</pre>
          *
-         * @return Instance of {@link WifiNetworkSuggestion}.
-         * @throws IllegalStateException on invalid params set.
+         * @return Instance of {@link WifiNetworkSuggestion}
+         * @throws IllegalStateException on invalid params set
+         * @see WifiNetworkSuggestion
          */
         public @NonNull WifiNetworkSuggestion build() {
             if (mSsid == null) {