Merge "Set allowTouches after the fling animation end" into rvc-dev
diff --git a/api/current.txt b/api/current.txt
index 41a74cf..952ccda 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12097,6 +12097,7 @@
     field public static final String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
     field public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     field public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
+    field public static final String FEATURE_CONTROLS = "android.software.controls";
     field public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
     field public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
     field public static final String FEATURE_ETHERNET = "android.hardware.ethernet";
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 1d9f20e..4db0f91 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -1256,7 +1256,8 @@
  */
 message ChargingStateChanged {
     // State of the battery, from frameworks/base/core/proto/android/os/enums.proto.
-    optional android.os.BatteryStatusEnum state = 1;
+    optional android.os.BatteryStatusEnum state = 1
+            [(state_field_option).exclusive_state = true, (state_field_option).nested = false];
 }
 
 /**
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 4d718ef..1e9cddbb 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3049,6 +3049,16 @@
     public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and
+     * {@link #hasSystemFeature}: The device supports a system interface for the user to select
+     * and bind device control services provided by applications.
+     *
+     * @see android.service.controls.ControlsProviderService
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_CONTROLS = "android.software.controls";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
      * the requisite hardware support to support reboot escrow of synthetic password for updates.
      *
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 321dc9e..958c7fb 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -92,10 +92,7 @@
             }
         }
 
-        if (!result.mDefaultStorage.startLoading()) {
-            // TODO(b/146080380): add incremental-specific error code
-            throw new IOException("Failed to start loading data for Incremental installation.");
-        }
+        result.startLoading();
 
         return result;
     }
@@ -144,6 +141,15 @@
     }
 
     /**
+     * Starts or re-starts loading of data.
+     */
+    public void startLoading() throws IOException {
+        if (!mDefaultStorage.startLoading()) {
+            throw new IOException("Failed to start loading data for Incremental installation.");
+        }
+    }
+
+    /**
      * Resets the states and unbinds storage instances for an installation session.
      * TODO(b/136132412): make sure unnecessary binds are removed but useful storages are kept
      */
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ae88ba5..e0bc764 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9936,6 +9936,11 @@
         * @hide */
        public static final String PACKAGE_VERIFIER_TIMEOUT = "verifier_timeout";
 
+        /** Timeout for app integrity verification.
+         * @hide */
+        public static final String APP_INTEGRITY_VERIFICATION_TIMEOUT =
+                "app_integrity_verification_timeout";
+
        /** Default response code for package verification.
         * @hide */
        public static final String PACKAGE_VERIFIER_DEFAULT_RESPONSE = "verifier_default_response";
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 7086dc0..3850781 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -275,12 +275,4 @@
         // ViewRoot will release mSurfaceControl for us.
         mViewRoot.die(false /* immediate */);
     }
-
-    /**
-     * Tell this viewroot to clean itself up.
-     * @hide
-     */
-    public void die() {
-        mViewRoot.die(false /* immediate */);
-    }
 }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index db6fe0f..bd811fc 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -134,6 +134,23 @@
     // we need to preserve the old one until the new one has drawn.
     SurfaceControl mDeferredDestroySurfaceControl;
     SurfaceControl mBackgroundControl;
+
+    /**
+     * We use this lock in SOME cases when reading or writing SurfaceControl,
+     * but use the following model so that the RenderThread can run locklessly
+     * in the position up-date case.
+     *
+     * 1. UI Thread can read from mSurfaceControl (use in Transactions) without
+     * holding the lock.
+     * 2. UI Thread will hold the lock when writing to mSurfaceControl (calling release
+     * or remove).
+     * 3. Render thread will also hold the lock when writing to mSurfaceControl (e.g.
+     * calling release from positionLost).
+     * 3. RenderNode.PositionUpdateListener::positionChanged will only be called
+     * when the UI thread is paused (blocked on the Render thread).
+     * 4. positionChanged thus will not be required to hold the lock as the
+     * UI thread is blocked, and the other writer is the RT itself.
+     */
     final Object mSurfaceControlLock = new Object();
     final Rect mTmpRect = new Rect();
 
@@ -1297,15 +1314,19 @@
                 (viewRoot != null ? viewRoot.getBLASTSyncTransaction() : mRtTransaction) :
                 mRtTransaction;
 
-            if (frameNumber > 0 && viewRoot !=  null && !useBLAST) {
-                if (viewRoot.mSurface.isValid()) {
-                    mRtTransaction.deferTransactionUntil(mSurfaceControl,
-                            viewRoot.getRenderSurfaceControl(), frameNumber);
-                }
-            }
-            t.hide(mSurfaceControl);
-
+            /**
+             * positionLost can be called while UI thread is un-paused so we
+             * need to hold the lock here.
+             */
             synchronized (mSurfaceControlLock) {
+                if (frameNumber > 0 && viewRoot !=  null && !useBLAST) {
+                    if (viewRoot.mSurface.isValid()) {
+                        mRtTransaction.deferTransactionUntil(mSurfaceControl,
+                                viewRoot.getRenderSurfaceControl(), frameNumber);
+                    }
+                }
+                t.hide(mSurfaceControl);
+
                 if (mRtReleaseSurfaces) {
                     mRtReleaseSurfaces = false;
                     mRtTransaction.remove(mSurfaceControl);
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index 4c72474..e4ac588 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -326,11 +326,13 @@
 
         @MainThread
         private void handleOnSurfacePackageReleased() {
-            mSurfacePackage = null;
-            try {
-                mInlineContentProvider.onSurfacePackageReleased();
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Error calling onSurfacePackageReleased(): " + e);
+            if (mSurfacePackage != null) {
+                try {
+                    mInlineContentProvider.onSurfacePackageReleased();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Error calling onSurfacePackageReleased(): " + e);
+                }
+                mSurfacePackage = null;
             }
         }
 
@@ -573,7 +575,7 @@
     };
 
     @DataClass.Generated(
-            time = 1588308946517L,
+            time = 1589396017700L,
             codegenVersion = "1.0.15",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
             inputSignatures = "private static final  java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\nprivate @com.android.internal.util.DataClass.ParcelWith(android.view.inputmethod.InlineSuggestion.InlineContentCallbackImplParceling.class) @android.annotation.Nullable android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl mInlineContentCallback\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic  void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nprivate static  boolean isValid(int,int,int)\nprivate synchronized  android.view.inputmethod.InlineSuggestion.InlineContentCallbackImpl getInlineContentCallback(android.content.Context,java.util.concurrent.Executor,java.util.function.Consumer<android.widget.inline.InlineContentView>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index f8522ed..f78ec7c 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -189,14 +189,29 @@
     public void onCloseWindow(WebView window) {}
 
     /**
-     * Tell the client to display a javascript alert dialog.  If the client
-     * returns {@code true}, WebView will assume that the client will handle the
-     * dialog.  If the client returns {@code false}, it will continue execution.
+     * Notify the host application that the web page wants to display a
+     * JavaScript {@code alert()} dialog.
+     * <p>The default behavior if this method returns {@code false} or is not
+     * overridden is to show a dialog containing the alert message and suspend
+     * JavaScript execution until the dialog is dismissed.
+     * <p>To show a custom dialog, the app should return {@code true} from this
+     * method, in which case the default dialog will not be shown and JavaScript
+     * execution will be suspended. The app should call
+     * {@code JsResult.confirm()} when the custom dialog is dismissed such that
+     * JavaScript execution can be resumed.
+     * <p>To suppress the dialog and allow JavaScript execution to
+     * continue, call {@code JsResult.confirm()} immediately and then return
+     * {@code true}.
+     * <p>Note that if the {@link WebChromeClient} is {@code null}, the default
+     * dialog will be suppressed and Javascript execution will continue
+     * immediately.
+     *
      * @param view The WebView that initiated the callback.
      * @param url The url of the page requesting the dialog.
      * @param message Message to be displayed in the window.
-     * @param result A JsResult to confirm that the user hit enter.
-     * @return boolean Whether the client will handle the alert dialog.
+     * @param result A JsResult to confirm that the user closed the window.
+     * @return boolean {@code true} if the request is handled or ignored.
+     * {@code false} if WebView needs to show the default dialog.
      */
     public boolean onJsAlert(WebView view, String url, String message,
             JsResult result) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 29914aa..226f5797 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -8315,23 +8315,6 @@
         return false;
     }
 
-    /**
-     * Returns true if pressing TAB in this field advances focus instead
-     * of inserting the character.  Insert tabs only in multi-line editors.
-     */
-    private boolean shouldAdvanceFocusOnTab() {
-        if (getKeyListener() != null && !mSingleLine && mEditor != null
-                && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
-                        == EditorInfo.TYPE_CLASS_TEXT) {
-            int multilineFlags = EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
-                    | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
-            if ((mEditor.mInputType & multilineFlags) != 0) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     private boolean isDirectionalNavigationKey(int keyCode) {
         switch(keyCode) {
             case KeyEvent.KEYCODE_DPAD_UP:
@@ -8400,9 +8383,8 @@
 
             case KeyEvent.KEYCODE_TAB:
                 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
-                    if (shouldAdvanceFocusOnTab()) {
-                        return KEY_EVENT_NOT_HANDLED;
-                    }
+                    // Tab is used to move focus.
+                    return KEY_EVENT_NOT_HANDLED;
                 }
                 break;
 
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 9c2df13..3084f2a 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -358,6 +358,7 @@
                 : isHttpSchemeAndViewAction(getTargetIntent());
 
         mSupportsAlwaysUseOption = supportsAlwaysUseOption;
+        mWorkProfileUserHandle = fetchWorkProfileUserProfile();
 
         // The last argument of createResolverListAdapter is whether to do special handling
         // of the last used choice to highlight it in the list.  We need to always
@@ -366,7 +367,6 @@
         // to handle. We also turn it off when the work tab is shown to simplify the UX.
         boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction()
                 && !shouldShowTabs();
-        mWorkProfileUserHandle = fetchWorkProfileUserProfile();
         mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
         if (configureContentView()) {
             return;
@@ -1612,6 +1612,7 @@
     }
 
     private void setupProfileTabs() {
+        maybeHideDivider();
         TabHost tabHost = findViewById(R.id.profile_tabhost);
         tabHost.setup();
         ViewPager viewPager = findViewById(R.id.profile_pager);
@@ -1660,6 +1661,17 @@
         findViewById(R.id.resolver_tab_divider).setVisibility(View.VISIBLE);
     }
 
+    private void maybeHideDivider() {
+        if (!isIntentPicker()) {
+            return;
+        }
+        final View divider = findViewById(R.id.divider);
+        if (divider == null) {
+            return;
+        }
+        divider.setVisibility(View.GONE);
+    }
+
     /**
      * Callback called when user changes the profile tab.
      * <p>This method is intended to be overridden by subclasses.
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 49c9302..ad6c7e8 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -132,6 +132,7 @@
                     }
                 };
     }
+
     private static final String TAG = "ScreenshotHelper";
 
     // Time until we give up on the screenshot & show an error instead.
@@ -146,8 +147,6 @@
         mContext = context;
     }
 
-
-
     /**
      * Request a screenshot be taken.
      *
@@ -284,8 +283,8 @@
                             break;
                         case SCREENSHOT_MSG_PROCESS_COMPLETE:
                             synchronized (mScreenshotLock) {
-                                if (mScreenshotConnection == myConn) {
-                                    mContext.unbindService(mScreenshotConnection);
+                                if (myConn != null && mScreenshotConnection == myConn) {
+                                    mContext.unbindService(myConn);
                                     mScreenshotConnection = null;
                                     mScreenshotService = null;
                                 }
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f3f3d47d..54d14f8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1475,7 +1475,7 @@
     <string name="permdesc_mediaLocation">Allows the app to read locations from your media collection.</string>
 
     <!-- Title shown when the system-provided biometric dialog is shown, asking the user to authenticate. [CHAR LIMIT=40] -->
-    <string name="biometric_dialog_default_title">Verify it\u2018s you</string>
+    <string name="biometric_dialog_default_title">Verify it\u2019s you</string>
     <!-- Message shown when biometric hardware is not available [CHAR LIMIT=50] -->
     <string name="biometric_error_hw_unavailable">Biometric hardware unavailable</string>
     <!-- Message shown when biometric authentication was canceled by the user [CHAR LIMIT=50] -->
@@ -3595,31 +3595,37 @@
 
     <!-- Notification body when new external media is detected [CHAR LIMIT=30] -->
     <string name="ext_media_new_notification_title">New <xliff:g id="name" example="SD card">%s</xliff:g></string>
+    <!-- Automotive specific notification body when new external media is detected [CHAR LIMIT=30] -->
+    <string name="ext_media_new_notification_title" product="automotive"><xliff:g id="name" example="SD card">%s</xliff:g> isn\u2019t working</string>
     <!-- Notification body when new external media is detected [CHAR LIMIT=NONE] -->
     <string name="ext_media_new_notification_message">Tap to set up</string>
-    <!-- Automotive specific notification body when new external media is detected. Empty because there is no fix action (b/151671685) [CHAR LIMIT=NONE] -->
-    <string name="ext_media_new_notification_message" product="automotive"></string>
+    <!-- Automotive specific notification body when new external media is detected. [CHAR LIMIT=NONE] -->
+    <string name="ext_media_new_notification_message" product="automotive">You may need to reformat the device. Tap to eject.</string>
 
     <!-- Notification body when external media is ready for use [CHAR LIMIT=NONE] -->
     <string name="ext_media_ready_notification_message">For transferring photos and media</string>
 
     <!-- Notification title when external media is unmountable (corrupt) [CHAR LIMIT=30] -->
     <string name="ext_media_unmountable_notification_title">Issue with <xliff:g id="name" example="SD card">%s</xliff:g></string>
+    <!-- Automotive specific notification title when external media is unmountable (corrupt) [CHAR LIMIT=30] -->
+    <string name="ext_media_unmountable_notification_title" product="automotive"><xliff:g id="name" example="SD card">%s</xliff:g> isn\u2019t working</string>
     <!-- Notification body when external media is unmountable (corrupt) [CHAR LIMIT=NONE] -->
     <string name="ext_media_unmountable_notification_message">Tap to fix</string>
     <!-- TV-specific notification body when external media is unmountable (corrupt) [CHAR LIMIT=NONE] -->
     <string name="ext_media_unmountable_notification_message" product="tv"><xliff:g id="name" example="SD card">%s</xliff:g> is corrupt. Select to fix.</string>
-    <!-- Automotive specific notification body when external media is unmountable (corrupt). Empty because there is no fix action (b/151671685) [CHAR LIMIT=NONE] -->
-    <string name="ext_media_unmountable_notification_message" product="automotive"></string>
+    <!-- Automotive specific notification body when external media is unmountable (corrupt) [CHAR LIMIT=NONE] -->
+    <string name="ext_media_unmountable_notification_message" product="automotive">You may need to reformat the device. Tap to eject.</string>
 
     <!-- Notification title when external media is unsupported [CHAR LIMIT=30] -->
     <string name="ext_media_unsupported_notification_title">Unsupported <xliff:g id="name" example="SD card">%s</xliff:g></string>
+    <!-- Automotive specific notification title when external media is unsupported [CHAR LIMIT=30] -->
+    <string name="ext_media_unsupported_notification_title" product="automotive"><xliff:g id="name" example="SD card">%s</xliff:g> isn\u2019t working</string>
     <!-- Notification body when external media is unsupported [CHAR LIMIT=NONE] -->
     <string name="ext_media_unsupported_notification_message">This device doesn\u2019t support this <xliff:g id="name" example="SD card">%s</xliff:g>. Tap to set up in a supported format.</string>
     <!-- TV-specific notification body when external media is unsupported [CHAR LIMIT=NONE] -->
     <string name="ext_media_unsupported_notification_message" product="tv">This device doesn\u2019t support this <xliff:g id="name" example="SD card">%s</xliff:g>. Select to set up in a supported format.</string>
-    <!-- Automotive specific notification body when external media is unsupported. No action is specified to fix (b/151671685) [CHAR LIMIT=NONE] -->
-    <string name="ext_media_unsupported_notification_message" product="automotive">This device doesn\u2019t support this <xliff:g id="name" example="SD card">%s</xliff:g>.</string>
+    <!-- Automotive specific notification body when external media is unsupported [CHAR LIMIT=NONE] -->
+    <string name="ext_media_unsupported_notification_message" product="automotive">You may need to reformat the device</string>
 
     <!-- Notification title when external media is unsafely removed [CHAR LIMIT=30] -->
     <string name="ext_media_badremoval_notification_title"><xliff:g id="name" example="SD card">%s</xliff:g> unexpectedly removed</string>
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index ecc3b4f..5c16772 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -28,29 +28,23 @@
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
-import static org.hamcrest.Matchers.equalTo;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assume.assumeTrue;
+import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
-import android.graphics.Insets;
-import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.view.WindowInsets.Side;
 import android.view.WindowInsets.Type;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-
 /**
  * Tests for {@link ViewRootImpl}
  *
@@ -62,59 +56,18 @@
 @RunWith(AndroidJUnit4.class)
 public class ViewRootImplTest {
 
-    private Context mContext;
-    private ViewRootImplAccessor mViewRootImpl;
+    private ViewRootImpl mViewRootImpl;
 
     @Before
     public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        final Context context = getInstrumentation().getTargetContext();
 
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mViewRootImpl = new ViewRootImplAccessor(
-                    new ViewRootImpl(mContext, mContext.getDisplayNoVerify()));
-        });
-    }
-
-    @Test
-    public void negativeInsets_areSetToZero() throws Exception {
-        assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
-
-        mViewRootImpl.getAttachInfo().getContentInsets().set(-10, -20, -30 , -40);
-        mViewRootImpl.getAttachInfo().getStableInsets().set(-10, -20, -30 , -40);
-        final WindowInsets insets = mViewRootImpl.getWindowInsets(true /* forceConstruct */);
-
-        assertThat(insets.getSystemWindowInsets(), equalTo(Insets.NONE));
-        assertThat(insets.getStableInsets(), equalTo(Insets.NONE));
-    }
-
-    @Test
-    public void negativeInsets_areSetToZero_positiveAreLeftAsIs() throws Exception {
-        assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
-
-        mViewRootImpl.getAttachInfo().getContentInsets().set(-10, 20, -30 , 40);
-        mViewRootImpl.getAttachInfo().getStableInsets().set(10, -20, 30 , -40);
-        final WindowInsets insets = mViewRootImpl.getWindowInsets(true /* forceConstruct */);
-
-        assertThat(insets.getSystemWindowInsets(), equalTo(Insets.of(0, 20, 0, 40)));
-        assertThat(insets.getStableInsets(), equalTo(Insets.of(10, 0, 30, 0)));
-    }
-
-    @Test
-    public void positiveInsets_areLeftAsIs() throws Exception {
-        assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
-
-        mViewRootImpl.getAttachInfo().getContentInsets().set(10, 20, 30 , 40);
-        mViewRootImpl.getAttachInfo().getStableInsets().set(10, 20, 30 , 40);
-        final WindowInsets insets = mViewRootImpl.getWindowInsets(true /* forceConstruct */);
-
-        assertThat(insets.getSystemWindowInsets(), equalTo(Insets.of(10, 20, 30, 40)));
-        assertThat(insets.getStableInsets(), equalTo(Insets.of(10, 20, 30, 40)));
+        getInstrumentation().runOnMainSync(() ->
+                mViewRootImpl = new ViewRootImpl(context, context.getDisplayNoVerify()));
     }
 
     @Test
     public void adjustLayoutParamsForCompatibility_layoutFullscreen() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
         attrs.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
         ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
@@ -125,8 +78,6 @@
 
     @Test
     public void adjustLayoutParamsForCompatibility_layoutInScreen() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
         attrs.flags = FLAG_LAYOUT_IN_SCREEN;
         ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
@@ -137,8 +88,6 @@
 
     @Test
     public void adjustLayoutParamsForCompatibility_layoutHideNavigation() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
         attrs.systemUiVisibility = SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
         ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
@@ -149,28 +98,22 @@
 
     @Test
     public void adjustLayoutParamsForCompatibility_toast() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_TOAST);
         ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
 
-        assertEquals(true, attrs.isFitInsetsIgnoringVisibility());
+        assertTrue(attrs.isFitInsetsIgnoringVisibility());
     }
 
     @Test
     public void adjustLayoutParamsForCompatibility_systemAlert() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_SYSTEM_ALERT);
         ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
 
-        assertEquals(true, attrs.isFitInsetsIgnoringVisibility());
+        assertTrue(attrs.isFitInsetsIgnoringVisibility());
     }
 
     @Test
     public void adjustLayoutParamsForCompatibility_fitSystemBars() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
         ViewRootImpl.adjustLayoutParamsForCompatibility(attrs);
 
@@ -180,8 +123,6 @@
 
     @Test
     public void adjustLayoutParamsForCompatibility_noAdjustLayout() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(TYPE_APPLICATION);
         final int types = Type.all();
         final int sides = Side.TOP | Side.LEFT;
@@ -201,11 +142,8 @@
 
     @Test
     public void adjustLayoutParamsForCompatibility_noAdjustAppearance() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
-        final ViewRootImpl viewRoot = mViewRootImpl.get();
-        final WindowInsetsController controller = viewRoot.getInsetsController();
-        final WindowManager.LayoutParams attrs = viewRoot.mWindowAttributes;
+        final WindowInsetsController controller = mViewRootImpl.getInsetsController();
+        final WindowManager.LayoutParams attrs = mViewRootImpl.mWindowAttributes;
         final int appearance = 0;
         controller.setSystemBarsAppearance(appearance, 0xffffffff);
         attrs.systemUiVisibility = SYSTEM_UI_FLAG_LOW_PROFILE
@@ -220,11 +158,8 @@
 
     @Test
     public void adjustLayoutParamsForCompatibility_noAdjustBehavior() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
-        final ViewRootImpl viewRoot = mViewRootImpl.get();
-        final WindowInsetsController controller = viewRoot.getInsetsController();
-        final WindowManager.LayoutParams attrs = viewRoot.mWindowAttributes;
+        final WindowInsetsController controller = mViewRootImpl.getInsetsController();
+        final WindowManager.LayoutParams attrs = mViewRootImpl.mWindowAttributes;
         final int behavior = BEHAVIOR_SHOW_BARS_BY_TOUCH;
         controller.setSystemBarsBehavior(behavior);
         attrs.systemUiVisibility = SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
@@ -234,59 +169,4 @@
         // setSystemBarsBehavior.
         assertEquals(behavior, controller.getSystemBarsBehavior());
     }
-
-    private static class ViewRootImplAccessor {
-
-        private final ViewRootImpl mViewRootImpl;
-
-        ViewRootImplAccessor(ViewRootImpl viewRootImpl) {
-            mViewRootImpl = viewRootImpl;
-        }
-
-        public ViewRootImpl get() {
-            return mViewRootImpl;
-        }
-
-        AttachInfoAccessor getAttachInfo() throws Exception {
-            return new AttachInfoAccessor(
-                    getField(mViewRootImpl, ViewRootImpl.class.getDeclaredField("mAttachInfo")));
-        }
-
-        WindowInsets getWindowInsets(boolean forceConstruct) throws Exception {
-            return (WindowInsets) invokeMethod(mViewRootImpl,
-                    ViewRootImpl.class.getDeclaredMethod("getWindowInsets", boolean.class),
-                    forceConstruct);
-        }
-
-        class AttachInfoAccessor {
-
-            private final Class<?> mClass;
-            private final Object mAttachInfo;
-
-            AttachInfoAccessor(Object attachInfo) throws Exception {
-                mAttachInfo = attachInfo;
-                mClass = ViewRootImpl.class.getClassLoader().loadClass(
-                        "android.view.View$AttachInfo");
-            }
-
-            Rect getContentInsets() throws Exception {
-                return (Rect) getField(mAttachInfo, mClass.getDeclaredField("mContentInsets"));
-            }
-
-            Rect getStableInsets() throws Exception {
-                return (Rect) getField(mAttachInfo, mClass.getDeclaredField("mStableInsets"));
-            }
-        }
-
-        private static Object getField(Object o, Field field) throws Exception {
-            field.setAccessible(true);
-            return field.get(o);
-        }
-
-        private static Object invokeMethod(Object o, Method method, Object... args)
-                throws Exception {
-            method.setAccessible(true);
-            return method.invoke(o, args);
-        }
-    }
 }
diff --git a/core/tests/overlaytests/host/Android.bp b/core/tests/overlaytests/host/Android.bp
index 2b38cca..a2fcef5 100644
--- a/core/tests/overlaytests/host/Android.bp
+++ b/core/tests/overlaytests/host/Android.bp
@@ -16,7 +16,7 @@
     name: "OverlayHostTests",
     srcs: ["src/**/*.java"],
     libs: ["tradefed"],
-    test_suites: ["device-tests"],
+    test_suites: ["general-tests"],
     target_required: [
         "OverlayHostTests_NonPlatformSignatureOverlay",
         "OverlayHostTests_PlatformSignatureStaticOverlay",
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0389639..bdb6bcc 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -324,6 +324,7 @@
         <permission name="android.permission.MANAGE_ROLLBACKS"/>
         <permission name="android.permission.MANAGE_USB"/>
         <permission name="android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS"/>
+        <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.MOUNT_FORMAT_FILESYSTEMS"/>
         <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java b/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
index 2bdb85f..03b61e0 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
@@ -52,6 +52,15 @@
             new CarAudioManager.CarVolumeCallback() {
                 @Override
                 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
+                    initVolumeDialogComponent();
+                }
+
+                @Override
+                public void onMasterMuteChanged(int zoneId, int flags) {
+                    initVolumeDialogComponent();
+                }
+
+                private void initVolumeDialogComponent() {
                     if (mVolumeDialogComponent == null) {
                         mMainHandler.post(() -> {
                             mVolumeDialogComponent = mVolumeDialogComponentLazy.get();
@@ -60,11 +69,6 @@
                         mCarAudioManager.unregisterCarVolumeCallback(mVolumeChangeCallback);
                     }
                 }
-
-                @Override
-                public void onMasterMuteChanged(int zoneId, int flags) {
-                    // ignored
-                }
             };
 
     private boolean mEnabled;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 1d06df0..008a433 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -475,5 +475,10 @@
         public void onRequestFailed(int reason) {
             dispatchOnRequestFailed(reason);
         }
+
+        @Override
+        public void onSessionUpdated(RoutingSessionInfo sessionInfo) {
+            dispatchDataChanged();
+        }
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 887a49b..a860ff1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -25,6 +25,7 @@
 import android.util.Log;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.bluetooth.A2dpProfile;
@@ -65,6 +66,7 @@
     }
 
     private final Collection<DeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
+    private final Object mMediaDevicesLock = new Object();
     @VisibleForTesting
     final MediaDeviceCallback mMediaDeviceCallback = new MediaDeviceCallback();
 
@@ -145,7 +147,14 @@
      * @param connectDevice the MediaDevice
      */
     public void connectDevice(MediaDevice connectDevice) {
-        final MediaDevice device = getMediaDeviceById(mMediaDevices, connectDevice.getId());
+        MediaDevice device = null;
+        synchronized (mMediaDevicesLock) {
+            device = getMediaDeviceById(mMediaDevices, connectDevice.getId());
+        }
+        if (device == null) {
+            Log.w(TAG, "connectDevice() connectDevice not in the list!");
+            return;
+        }
         if (device instanceof BluetoothMediaDevice) {
             final CachedBluetoothDevice cachedDevice =
                     ((BluetoothMediaDevice) device).getCachedDevice();
@@ -184,15 +193,18 @@
      * Start scan connected MediaDevice
      */
     public void startScan() {
-        mMediaDevices.clear();
+        synchronized (mMediaDevicesLock) {
+            mMediaDevices.clear();
+        }
         mInfoMediaManager.registerCallback(mMediaDeviceCallback);
         mInfoMediaManager.startScan();
     }
 
     void dispatchDeviceListUpdate() {
-        Collections.sort(mMediaDevices, COMPARATOR);
+        final List<MediaDevice> mediaDevices = new ArrayList<>(mMediaDevices);
+        Collections.sort(mediaDevices, COMPARATOR);
         for (DeviceCallback callback : getCallbacks()) {
-            callback.onDeviceListUpdate(new ArrayList<>(mMediaDevices));
+            callback.onDeviceListUpdate(mediaDevices);
         }
     }
 
@@ -241,9 +253,11 @@
      * @return MediaDevice
      */
     public MediaDevice getMediaDeviceById(String id) {
-        for (MediaDevice mediaDevice : mMediaDevices) {
-            if (TextUtils.equals(mediaDevice.getId(), id)) {
-                return mediaDevice;
+        synchronized (mMediaDevicesLock) {
+            for (MediaDevice mediaDevice : mMediaDevices) {
+                if (TextUtils.equals(mediaDevice.getId(), id)) {
+                    return mediaDevice;
+                }
             }
         }
         Log.i(TAG, "Unable to find device " + id);
@@ -255,6 +269,7 @@
      *
      * @return MediaDevice
      */
+    @Nullable
     public MediaDevice getCurrentConnectedDevice() {
         return mCurrentConnectedDevice;
     }
@@ -367,17 +382,19 @@
     }
 
     private MediaDevice updateCurrentConnectedDevice() {
-        MediaDevice phoneMediaDevice = null;
-        for (MediaDevice device : mMediaDevices) {
-            if (device instanceof  BluetoothMediaDevice) {
-                if (isActiveDevice(((BluetoothMediaDevice) device).getCachedDevice())) {
+        synchronized (mMediaDevicesLock) {
+            for (MediaDevice device : mMediaDevices) {
+                if (device instanceof BluetoothMediaDevice) {
+                    if (isActiveDevice(((BluetoothMediaDevice) device).getCachedDevice())) {
+                        return device;
+                    }
+                } else if (device instanceof PhoneMediaDevice) {
                     return device;
                 }
-            } else if (device instanceof PhoneMediaDevice) {
-                phoneMediaDevice = device;
             }
         }
-        return mMediaDevices.contains(phoneMediaDevice) ? phoneMediaDevice : null;
+        Log.w(TAG, "updateCurrentConnectedDevice() can't found current connected device");
+        return null;
     }
 
     private boolean isActiveDevice(CachedBluetoothDevice device) {
@@ -392,17 +409,26 @@
     class MediaDeviceCallback implements MediaManager.MediaDeviceCallback {
         @Override
         public void onDeviceAdded(MediaDevice device) {
-            if (!mMediaDevices.contains(device)) {
-                mMediaDevices.add(device);
+            boolean isAdded = false;
+            synchronized (mMediaDevicesLock) {
+                if (!mMediaDevices.contains(device)) {
+                    mMediaDevices.add(device);
+                    isAdded = true;
+                }
+            }
+
+            if (isAdded) {
                 dispatchDeviceListUpdate();
             }
         }
 
         @Override
         public void onDeviceListAdded(List<MediaDevice> devices) {
-            mMediaDevices.clear();
-            mMediaDevices.addAll(devices);
-            mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
+            synchronized (mMediaDevicesLock) {
+                mMediaDevices.clear();
+                mMediaDevices.addAll(devices);
+                mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
+            }
 
             final MediaDevice infoMediaDevice = mInfoMediaManager.getCurrentConnectedDevice();
             mCurrentConnectedDevice = infoMediaDevice != null
@@ -469,30 +495,42 @@
 
         @Override
         public void onDeviceRemoved(MediaDevice device) {
-            if (mMediaDevices.contains(device)) {
-                mMediaDevices.remove(device);
+            boolean isRemoved = false;
+            synchronized (mMediaDevicesLock) {
+                if (mMediaDevices.contains(device)) {
+                    mMediaDevices.remove(device);
+                    isRemoved = true;
+                }
+            }
+            if (isRemoved) {
                 dispatchDeviceListUpdate();
             }
         }
 
         @Override
         public void onDeviceListRemoved(List<MediaDevice> devices) {
-            mMediaDevices.removeAll(devices);
+            synchronized (mMediaDevicesLock) {
+                mMediaDevices.removeAll(devices);
+            }
             dispatchDeviceListUpdate();
         }
 
         @Override
         public void onConnectedDeviceChanged(String id) {
-            MediaDevice connectDevice = getMediaDeviceById(mMediaDevices, id);
+            MediaDevice connectDevice = null;
+            synchronized (mMediaDevicesLock) {
+                connectDevice = getMediaDeviceById(mMediaDevices, id);
+            }
             connectDevice = connectDevice != null
                     ? connectDevice : updateCurrentConnectedDevice();
-            if (connectDevice != null) {
-                connectDevice.setState(MediaDeviceState.STATE_CONNECTED);
-            }
 
             mCurrentConnectedDevice = connectDevice;
-            dispatchSelectedDeviceStateChanged(mCurrentConnectedDevice,
-                    MediaDeviceState.STATE_CONNECTED);
+            if (connectDevice != null) {
+                connectDevice.setState(MediaDeviceState.STATE_CONNECTED);
+
+                dispatchSelectedDeviceStateChanged(mCurrentConnectedDevice,
+                        MediaDeviceState.STATE_CONNECTED);
+            }
         }
 
         @Override
@@ -502,9 +540,11 @@
 
         @Override
         public void onRequestFailed(int reason) {
-            for (MediaDevice device : mMediaDevices) {
-                if (device.getState() == MediaDeviceState.STATE_CONNECTING) {
-                    device.setState(MediaDeviceState.STATE_CONNECTING_FAILED);
+            synchronized (mMediaDevicesLock) {
+                for (MediaDevice device : mMediaDevices) {
+                    if (device.getState() == MediaDeviceState.STATE_CONNECTING) {
+                        device.setState(MediaDeviceState.STATE_CONNECTING_FAILED);
+                    }
                 }
             }
             dispatchOnRequestFailed(reason);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 99c568a..734866f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -625,6 +625,15 @@
     }
 
     @Test
+    public void onSessionUpdated_shouldDispatchDataChanged() {
+        mInfoMediaManager.registerCallback(mCallback);
+
+        mInfoMediaManager.mMediaRouterCallback.onSessionUpdated(mock(RoutingSessionInfo.class));
+
+        verify(mCallback).onDeviceAttributesChanged();
+    }
+
+    @Test
     public void addMediaDevice_verifyDeviceTypeCanCorrespondToMediaDevice() {
         final MediaRoute2Info route2Info = mock(MediaRoute2Info.class);
         final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 29c31ea..e537b76 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -588,8 +588,9 @@
                     Settings.Global.POWER_BUTTON_VERY_LONG_PRESS,
                     Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, // Temporary for R beta
                     Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
-                    Settings.Global.ADVANCED_BATTERY_USAGE_AMOUNT,
-                    Settings.Global.CACHED_APPS_FREEZER_ENABLED);
+                    Settings.Global.CACHED_APPS_FREEZER_ENABLED,
+                    Settings.Global.APP_INTEGRITY_VERIFICATION_TIMEOUT,
+                    Settings.Global.ADVANCED_BATTERY_USAGE_AMOUNT);
 
     private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
              newHashSet(
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 96caf31..83d7218 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -233,6 +233,9 @@
     <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
     <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
 
+    <!-- Permission required for CTS test - BatterySaverTest -->
+    <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
+
     <!-- Permission required for CTS test - UiModeManagerTest -->
     <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
 
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 61b1e30..c4ea2e9 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -259,6 +259,9 @@
     <!-- Permission to register process observer -->
     <uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER"/>
 
+    <!-- Restore settings (used by QS) even if they have been modified -->
+    <uses-permission android:name="android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE" />
+
     <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/res/drawable/control_no_favorites_background.xml b/packages/SystemUI/res/drawable/control_no_favorites_background.xml
index 947c77b..d895dd0 100644
--- a/packages/SystemUI/res/drawable/control_no_favorites_background.xml
+++ b/packages/SystemUI/res/drawable/control_no_favorites_background.xml
@@ -16,7 +16,22 @@
 * limitations under the License.
 */
 -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
-  <stroke android:width="1dp" android:color="@*android:color/foreground_material_dark"/>
-  <corners android:radius="@dimen/control_corner_radius" />
-</shape>
+<!-- Should be kept in sync with the wallet plugin, as both share a similar
+     design: packages/apps/QuickAccessWallet -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+    <item android:id="@android:id/mask">
+        <shape android:shape="rectangle">
+            <solid android:color="#000000" />
+            <corners android:radius="@dimen/control_corner_radius" />
+        </shape>
+    </item>
+    <item>
+        <shape>
+            <stroke
+                android:width="1dp"
+                android:color="#4DFFFFFF" />
+            <corners android:radius="@dimen/control_corner_radius"/>
+        </shape>
+    </item>
+</ripple>
diff --git a/packages/SystemUI/res/layout/bubble_overflow_view.xml b/packages/SystemUI/res/layout/bubble_overflow_view.xml
index 1ed1f07..1218fba 100644
--- a/packages/SystemUI/res/layout/bubble_overflow_view.xml
+++ b/packages/SystemUI/res/layout/bubble_overflow_view.xml
@@ -30,9 +30,8 @@
 
     <TextView
         android:id="@+id/bubble_view_name"
-        android:fontFamily="@*android:string/config_bodyFontFamily"
-        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"
-        android:textColor="?android:attr/textColorSecondary"
+        android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+        android:textSize="13sp"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:maxLines="1"
diff --git a/packages/SystemUI/res/layout/controls_icon.xml b/packages/SystemUI/res/layout/controls_icon.xml
index cc46ced..12bc5f6 100644
--- a/packages/SystemUI/res/layout/controls_icon.xml
+++ b/packages/SystemUI/res/layout/controls_icon.xml
@@ -19,8 +19,8 @@
 
 <ImageView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="28dp"
-    android:layout_height="28dp"
+    android:layout_width="24dp"
+    android:layout_height="24dp"
     android:scaleType="fitCenter"
-    android:layout_marginLeft="2dp"
-    android:layout_marginRight="2dp" />
+    android:layout_marginLeft="5dp"
+    android:layout_marginRight="5dp" />
diff --git a/packages/SystemUI/res/layout/controls_no_favorites.xml b/packages/SystemUI/res/layout/controls_no_favorites.xml
index 4128230..1b24ee9 100644
--- a/packages/SystemUI/res/layout/controls_no_favorites.xml
+++ b/packages/SystemUI/res/layout/controls_no_favorites.xml
@@ -24,11 +24,10 @@
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical"
-      android:paddingTop="40dp"
-      android:paddingBottom="40dp"
-      android:layout_marginLeft="10dp"
-      android:layout_marginRight="10dp"
-      android:layout_marginTop="@dimen/controls_top_margin"
+      android:paddingVertical="@dimen/controls_setup_vertical_padding"
+      android:layout_marginLeft="@dimen/global_actions_side_margin"
+      android:layout_marginRight="@dimen/global_actions_side_margin"
+      android:layout_marginTop="@dimen/controls_setup_top_margin"
       android:background="@drawable/control_no_favorites_background">
 
     <LinearLayout
@@ -37,7 +36,7 @@
         android:layout_height="wrap_content"
         android:layout_gravity="center"
         android:orientation="horizontal"
-        android:paddingBottom="8dp" />
+        android:layout_marginBottom="16dp" />
 
     <TextView
         style="@style/TextAppearance.ControlSetup.Title"
diff --git a/packages/SystemUI/res/layout/partial_conversation_info.xml b/packages/SystemUI/res/layout/partial_conversation_info.xml
index 2401dfb..a261114 100644
--- a/packages/SystemUI/res/layout/partial_conversation_info.xml
+++ b/packages/SystemUI/res/layout/partial_conversation_info.xml
@@ -144,25 +144,36 @@
         android:clipToPadding="false"
         android:orientation="vertical">
 
-        <LinearLayout
+        <com.android.systemui.statusbar.notification.row.ButtonLinearLayout
+            android:id="@+id/settings_link"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:clipChildren="false"
-            android:clipToPadding="false"
-            android:orientation="horizontal">
-            <ImageView
-                android:layout_height="wrap_content"
-                android:layout_width="wrap_content"
-                android:contentDescription="@null"
-                android:src="@drawable/ic_info"
-                android:tint="?android:attr/textColorPrimary"
-                android:layout_marginEnd="8dp"/>
-            <TextView
-                android:id="@+id/non_configurable_text"
+            android:padding="@dimen/notification_importance_button_padding"
+            android:clickable="true"
+            android:focusable="true"
+            android:background="@drawable/notification_guts_priority_button_bg"
+            android:orientation="vertical">
+
+            <LinearLayout
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                style="@style/TextAppearance.NotificationImportanceChannelGroup" />
-        </LinearLayout>
+                android:clipChildren="false"
+                android:clipToPadding="false"
+                android:orientation="horizontal">
+                <ImageView
+                    android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:contentDescription="@null"
+                    android:src="@drawable/ic_info"
+                    android:tint="?android:attr/textColorPrimary"
+                    android:layout_marginEnd="8dp"/>
+                <TextView
+                    android:id="@+id/non_configurable_text"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    style="@style/TextAppearance.NotificationImportanceChannelGroup" />
+            </LinearLayout>
+        </com.android.systemui.statusbar.notification.row.ButtonLinearLayout>
 
         <RelativeLayout
             android:id="@+id/bottom_buttons"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c2694aa..a2e11a7 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1281,6 +1281,10 @@
     <dimen name="control_status_padding">3dp</dimen>
     <fraction name="controls_toggle_bg_intensity">5%</fraction>
     <fraction name="controls_dimmed_alpha">40%</fraction>
+    <dimen name="controls_setup_top_margin">16dp</dimen>
+    <dimen name="controls_setup_title">22sp</dimen>
+    <dimen name="controls_setup_subtitle">14sp</dimen>
+    <dimen name="controls_setup_vertical_padding">52dp</dimen>
 
     <!-- Home Controls activity view detail panel-->
     <dimen name="controls_activity_view_top_offset">100dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ec29622..8c10f61 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2698,6 +2698,20 @@
         <item quantity="other"><xliff:g id="number" example="3">%s</xliff:g> controls added.</item>
     </plurals>
 
+    <!-- Removed control in management screen [CHAR LIMIT=20] -->
+    <string name="controls_removed">Removed</string>
+
+    <!-- a11y state description for a control that is currently favorited [CHAR LIMIT=NONE] -->
+    <string name="accessibility_control_favorite">Favorited</string>
+    <!-- a11y state description for a control that is currently favorited with its position [CHAR LIMIT=NONE] -->
+    <string name="accessibility_control_favorite_position">Favorited, position <xliff:g id="number" example="1">%d</xliff:g></string>
+    <!-- a11y state description for a control that is currently not favorited [CHAR LIMIT=NONE] -->
+    <string name="accessibility_control_not_favorite">Unfavorited</string>
+    <!-- a11y action to favorite a control. It will read as "Double-tap to favorite" in screen readers [CHAR LIMIT=NONE] -->
+    <string name="accessibility_control_change_favorite">favorite</string>
+    <!-- a11y action to unfavorite a control. It will read as "Double-tap to unfavorite" in screen readers [CHAR LIMIT=NONE] -->
+    <string name="accessibility_control_change_unfavorite">unfavorite</string>
+
     <!-- Controls management controls screen default title [CHAR LIMIT=30] -->
     <string name="controls_favorite_default_title">Controls</string>
     <!-- Controls management controls screen subtitle [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f0edd63..c26d1f4 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -765,11 +765,11 @@
     </style>
 
     <style name="TextAppearance.ControlSetup.Title">
-        <item name="android:textSize">25sp</item>
+        <item name="android:textSize">@dimen/controls_setup_title</item>
     </style>
 
     <style name="TextAppearance.ControlSetup.Subtitle">
-        <item name="android:textSize">16sp</item>
+        <item name="android:textSize">@dimen/controls_setup_subtitle</item>
     </style>
 
     <!-- The attributes used for title (textAppearanceLarge) and message (textAppearanceMedium)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
index 8bd7c79..30156a0 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/SurfaceViewRequestReceiver.java
@@ -58,7 +58,7 @@
      */
     public void onReceive(Context context, Bundle bundle, View view, Size viewSize) {
         if (mSurfaceControlViewHost != null) {
-            mSurfaceControlViewHost.die();
+            mSurfaceControlViewHost.release();
         }
 
         SurfaceControl surfaceControl = SurfaceViewRequestUtils.getSurfaceControl(bundle);
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index 0106609..b1e1434 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -19,8 +19,6 @@
 import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
 import static android.telephony.PhoneStateListener.LISTEN_NONE;
 
-import static com.android.systemui.DejankUtils.whitelistIpcs;
-
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -47,6 +45,7 @@
 
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.inject.Inject;
 
@@ -61,9 +60,11 @@
 
     private final boolean mIsEmergencyCallCapable;
     private final Handler mMainHandler;
+    private final Handler mBgHandler;
     private boolean mTelephonyCapable;
     private boolean mShowMissingSim;
     private boolean mShowAirplaneMode;
+    private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
     @VisibleForTesting
     protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private WifiManager mWifiManager;
@@ -78,12 +79,14 @@
             new WakefulnessLifecycle.Observer() {
                 @Override
                 public void onFinishedWakingUp() {
-                    if (mCarrierTextCallback != null) mCarrierTextCallback.finishedWakingUp();
+                    final CarrierTextCallback callback = mCarrierTextCallback;
+                    if (callback != null) callback.finishedWakingUp();
                 }
 
                 @Override
                 public void onStartedGoingToSleep() {
-                    if (mCarrierTextCallback != null) mCarrierTextCallback.startedGoingToSleep();
+                    final CarrierTextCallback callback = mCarrierTextCallback;
+                    if (callback != null) callback.startedGoingToSleep();
                 }
             };
 
@@ -131,7 +134,7 @@
         @Override
         public void onActiveDataSubscriptionIdChanged(int subId) {
             mActiveMobileDataSubscription = subId;
-            if (mKeyguardUpdateMonitor != null) {
+            if (mNetworkSupported.get() && mCarrierTextCallback != null) {
                 updateCarrierText();
             }
         }
@@ -173,6 +176,17 @@
         mSimSlotsNumber = getTelephonyManager().getSupportedModemCount();
         mSimErrorState = new boolean[mSimSlotsNumber];
         mMainHandler = Dependency.get(Dependency.MAIN_HANDLER);
+        mBgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+        mBgHandler.post(() -> {
+            boolean supported = ConnectivityManager.from(mContext).isNetworkSupported(
+                    ConnectivityManager.TYPE_MOBILE);
+            if (supported && mNetworkSupported.compareAndSet(false, supported)) {
+                // This will set/remove the listeners appropriately. Note that it will never double
+                // add the listeners.
+                handleSetListening(mCarrierTextCallback);
+            }
+        });
     }
 
     private TelephonyManager getTelephonyManager() {
@@ -221,48 +235,51 @@
     }
 
     /**
-     * Sets the listening status of this controller. If the callback is null, it is set to
-     * not listening
+     * This may be called internally after retrieving the correct value of {@code mNetworkSupported}
+     * (assumed false to start). In that case, the following happens:
+     * <ul>
+     *     <li> If there was a registered callback, and the network is supported, it will register
+     *          listeners.
+     *     <li> If there was not a registered callback, it will try to remove unregistered listeners
+     *          which is a no-op
+     * </ul>
      *
-     * @param callback Callback to provide text updates
+     * This call will always be processed in a background thread.
      */
-    public void setListening(CarrierTextCallback callback) {
+    private void handleSetListening(CarrierTextCallback callback) {
         TelephonyManager telephonyManager = getTelephonyManager();
         if (callback != null) {
             mCarrierTextCallback = callback;
-            // TODO(b/140034799)
-            if (whitelistIpcs(() -> ConnectivityManager.from(mContext).isNetworkSupported(
-                    ConnectivityManager.TYPE_MOBILE))) {
-                mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+            if (mNetworkSupported.get()) {
                 // Keyguard update monitor expects callbacks from main thread
-                mMainHandler.post(() -> {
-                    if (mKeyguardUpdateMonitor != null) {
-                        mKeyguardUpdateMonitor.registerCallback(mCallback);
-                    }
-                });
+                mMainHandler.post(() -> mKeyguardUpdateMonitor.registerCallback(mCallback));
                 mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
                 telephonyManager.listen(mPhoneStateListener,
                         LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
             } else {
                 // Don't listen and clear out the text when the device isn't a phone.
-                mKeyguardUpdateMonitor = null;
-                callback.updateCarrierInfo(new CarrierTextCallbackInfo("", null, false, null));
+                mMainHandler.post(() -> callback.updateCarrierInfo(
+                        new CarrierTextCallbackInfo("", null, false, null)
+                ));
             }
         } else {
             mCarrierTextCallback = null;
-            if (mKeyguardUpdateMonitor != null) {
-                // Keyguard update monitor expects callbacks from main thread
-                mMainHandler.post(() -> {
-                    if (mKeyguardUpdateMonitor != null) {
-                        mKeyguardUpdateMonitor.removeCallback(mCallback);
-                    }
-                });
-                mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
-            }
+            mMainHandler.post(() -> mKeyguardUpdateMonitor.removeCallback(mCallback));
+            mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
             telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
         }
     }
 
+    /**
+     * Sets the listening status of this controller. If the callback is null, it is set to
+     * not listening.
+     *
+     * @param callback Callback to provide text updates
+     */
+    public void setListening(CarrierTextCallback callback) {
+        mBgHandler.post(() -> handleSetListening(callback));
+    }
+
     protected List<SubscriptionInfo> getSubscriptionInfo() {
         return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
     }
@@ -500,7 +517,7 @@
      */
     private CarrierTextController.StatusMode getStatusForIccState(int simState) {
         final boolean missingAndNotProvisioned =
-                !Dependency.get(KeyguardUpdateMonitor.class).isDeviceProvisioned()
+                !mKeyguardUpdateMonitor.isDeviceProvisioned()
                         && (simState == TelephonyManager.SIM_STATE_ABSENT
                         || simState == TelephonyManager.SIM_STATE_PERM_DISABLED);
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 9d1dfa7..1dd6409 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -34,7 +34,6 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.WindowInsets.Type;
 import android.view.WindowManager;
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
@@ -635,7 +634,6 @@
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setTitle("BiometricPrompt");
         lp.token = windowToken;
-        lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars());
         return lp;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 6a5e94c..887f1a7 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -198,6 +198,11 @@
     /** Last known orientation, used to detect orientation changes in {@link #onConfigChanged}. */
     private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
 
+    /**
+     * Last known screen density, used to detect display size changes in {@link #onConfigChanged}.
+     */
+    private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
+
     private boolean mInflateSynchronously;
 
     // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
@@ -705,9 +710,16 @@
 
     @Override
     public void onConfigChanged(Configuration newConfig) {
-        if (mStackView != null && newConfig != null && newConfig.orientation != mOrientation) {
-            mOrientation = newConfig.orientation;
-            mStackView.onOrientationChanged(newConfig.orientation);
+        if (mStackView != null && newConfig != null) {
+            if (newConfig.orientation != mOrientation) {
+                mOrientation = newConfig.orientation;
+                mStackView.onOrientationChanged(newConfig.orientation);
+            }
+            if (newConfig.densityDpi != mDensityDpi) {
+                mDensityDpi = newConfig.densityDpi;
+                mBubbleIconFactory = new BubbleIconFactory(mContext);
+                mStackView.onDisplaySizeChanged();
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
index e96bef3..f4eb580 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
@@ -58,12 +58,13 @@
     public BubbleOverflow(Context context) {
         mContext = context;
         mInflater = LayoutInflater.from(context);
-        mBitmapSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size);
-        mIconBitmapSize = mContext.getResources().getDimensionPixelSize(
-                R.dimen.bubble_overflow_icon_bitmap_size);
     }
 
     void setUpOverflow(ViewGroup parentViewGroup, BubbleStackView stackView) {
+        mBitmapSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size);
+        mIconBitmapSize = mContext.getResources().getDimensionPixelSize(
+                R.dimen.bubble_overflow_icon_bitmap_size);
+
         mExpandedView = (BubbleExpandedView) mInflater.inflate(
                 R.layout.bubble_expanded_view, parentViewGroup /* root */,
                 false /* attachToRoot */);
@@ -74,6 +75,7 @@
     }
 
     void updateIcon(Context context, ViewGroup parentViewGroup) {
+        mContext = context;
         mInflater = LayoutInflater.from(context);
         mOverflowBtn = (BadgedImageView) mInflater.inflate(R.layout.bubble_overflow_button,
                 parentViewGroup /* root */,
@@ -87,7 +89,7 @@
         ta.recycle();
 
         TypedValue typedValue = new TypedValue();
-        context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
+        mContext.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
         int colorAccent = mContext.getColor(typedValue.resourceId);
         mOverflowBtn.getDrawable().setTint(colorAccent);
         mDotColor = colorAccent;
@@ -97,7 +99,7 @@
                 mBitmapSize - mIconBitmapSize /* inset */);
         AdaptiveIconDrawable adaptiveIconDrawable = new AdaptiveIconDrawable(bg, fg);
 
-        BubbleIconFactory iconFactory = new BubbleIconFactory(context);
+        BubbleIconFactory iconFactory = new BubbleIconFactory(mContext);
         mIcon = iconFactory.createBadgedIconBitmap(adaptiveIconDrawable,
                 null /* user */,
                 true /* shrinkNonAdaptiveIcons */).icon;
@@ -106,7 +108,7 @@
                 null /* outBounds */, null /* path */, null /* outMaskShape */);
         float radius = DEFAULT_PATH_SIZE / 2f;
         mPath = PathParser.createPathFromPathData(
-                context.getResources().getString(com.android.internal.R.string.config_icon_mask));
+                mContext.getResources().getString(com.android.internal.R.string.config_icon_mask));
         Matrix matrix = new Matrix();
         matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
                 radius /* pivot y */);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 0074249..08ec789 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -41,6 +41,7 @@
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.internal.util.ContrastColorUtil;
 import com.android.systemui.R;
 
 import java.util.ArrayList;
@@ -214,6 +215,8 @@
     @Override
     public BubbleOverflowAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
             int viewType) {
+
+        // Set layout for overflow bubble view.
         LinearLayout overflowView = (LinearLayout) LayoutInflater.from(parent.getContext())
                 .inflate(R.layout.bubble_overflow_view, parent, false);
         LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
@@ -222,6 +225,18 @@
         params.width = mWidth;
         params.height = mHeight;
         overflowView.setLayoutParams(params);
+
+        // Ensure name has enough contrast.
+        final TypedArray ta = mContext.obtainStyledAttributes(
+                new int[]{android.R.attr.colorBackgroundFloating, android.R.attr.textColorPrimary});
+        final int bgColor = ta.getColor(0, Color.WHITE);
+        int textColor = ta.getColor(1, Color.BLACK);
+        textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true);
+        ta.recycle();
+
+        TextView viewName = overflowView.findViewById(R.id.bubble_view_name);
+        viewName.setTextColor(textColor);
+
         return new ViewHolder(overflowView);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index cb6e736..88f5eb0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -445,6 +445,10 @@
             final boolean clickedBubbleIsCurrentlyExpandedBubble =
                     clickedBubble.getKey().equals(mExpandedBubble.getKey());
 
+            if (isExpanded()) {
+                mExpandedAnimationController.onGestureFinished();
+            }
+
             if (isExpanded() && !clickedBubbleIsCurrentlyExpandedBubble) {
                 if (clickedBubble != mBubbleData.getSelectedBubble()) {
                     // Select the clicked bubble.
@@ -464,7 +468,6 @@
                     mBubbleData.setExpanded(!mBubbleData.isExpanded());
                 }
             }
-            mExpandedAnimationController.onGestureFinished();
         }
     };
 
@@ -787,8 +790,8 @@
 
         mOrientationChangedListener =
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                    mExpandedAnimationController.updateOrientation(mOrientation, mDisplaySize);
-                    mStackAnimationController.updateOrientation(mOrientation);
+                    mExpandedAnimationController.updateResources(mOrientation, mDisplaySize);
+                    mStackAnimationController.updateResources(mOrientation);
 
                     // Reposition & adjust the height for new orientation
                     if (mIsExpanded) {
@@ -1004,7 +1007,7 @@
             mBubbleOverflow.setUpOverflow(mBubbleContainer, this);
         } else {
             mBubbleContainer.removeView(mBubbleOverflow.getBtn());
-            mBubbleOverflow.updateIcon(mContext, this);
+            mBubbleOverflow.updateIcon(mContext,this);
             overflowBtnIndex = mBubbleContainer.getChildCount();
         }
         mBubbleContainer.addView(mBubbleOverflow.getBtn(), overflowBtnIndex,
@@ -1051,6 +1054,28 @@
         mShowingManage = false;
     }
 
+    /** Respond to the display size change by recalculating view size and location. */
+    public void onDisplaySizeChanged() {
+        setUpOverflow();
+
+        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
+        wm.getDefaultDisplay().getRealSize(mDisplaySize);
+        Resources res = getContext().getResources();
+        mStatusBarHeight = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.status_bar_height);
+        mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
+        mBubbleSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
+        for (Bubble b : mBubbleData.getBubbles()) {
+            if (b.getIconView() == null) {
+                Log.d(TAG, "Display size changed. Icon null: " + b);
+                continue;
+            }
+            b.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize));
+        }
+        mExpandedAnimationController.updateResources(mOrientation, mDisplaySize);
+        mStackAnimationController.updateResources(mOrientation);
+    }
+
     @Override
     public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
         inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
@@ -1291,7 +1316,7 @@
         Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
     }
 
-    private void updateOverflowBtnVisibility(boolean apply) {
+    private void updateOverflowBtnVisibility() {
         if (!BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
             return;
         }
@@ -1300,11 +1325,6 @@
                 Log.d(TAG, "Show overflow button.");
             }
             mBubbleOverflow.setBtnVisible(VISIBLE);
-            if (apply) {
-                mExpandedAnimationController.expandFromStack(() -> {
-                    updatePointerPosition();
-                } /* after */);
-            }
         } else {
             if (DEBUG_BUBBLE_STACK_VIEW) {
                 Log.d(TAG, "Collapsed. Hide overflow button.");
@@ -1564,7 +1584,7 @@
             Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
                     mExpandedBubble));
         }
-        updateOverflowBtnVisibility(/* apply */ false);
+        updateOverflowBtnVisibility();
         mBubbleContainer.cancelAllAnimations();
         mExpandedAnimationController.collapseBackToStack(
                 mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
@@ -1588,7 +1608,7 @@
         beforeExpandedViewAnimation();
 
         mBubbleContainer.setActiveController(mExpandedAnimationController);
-        updateOverflowBtnVisibility(/* apply */ false);
+        updateOverflowBtnVisibility();
         mExpandedAnimationController.expandFromStack(() -> {
             updatePointerPosition();
             afterExpandedViewAnimation();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 35406c7..f57cf42 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -118,7 +118,7 @@
 
     public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
             int orientation) {
-        updateOrientation(orientation, displaySize);
+        updateResources(orientation, displaySize);
         mExpandedViewPadding = expandedViewPadding;
     }
 
@@ -168,7 +168,7 @@
      * @param orientation Landscape or portrait.
      * @param displaySize Updated display size.
      */
-    public void updateOrientation(int orientation, Point displaySize) {
+    public void updateResources(int orientation, Point displaySize) {
         mScreenOrientation = orientation;
         mDisplaySize = displaySize;
         if (mLayout != null) {
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 5f3a2bd..2cfe1dd 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -809,7 +809,7 @@
      * Update effective screen width based on current orientation.
      * @param orientation Landscape or portrait.
      */
-    public void updateOrientation(int orientation) {
+    public void updateResources(int orientation) {
         if (mLayout != null) {
             Resources res = mLayout.getContext().getResources();
             mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
index 79dd9ed..4b283d6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -23,9 +23,14 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.CheckBox
 import android.widget.ImageView
+import android.widget.Switch
 import android.widget.TextView
+import androidx.core.view.AccessibilityDelegateCompat
+import androidx.core.view.ViewCompat
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
 import androidx.recyclerview.widget.GridLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import com.android.systemui.R
@@ -72,7 +77,8 @@
                         elevation = this@ControlAdapter.elevation
                         background = parent.context.getDrawable(
                                 R.drawable.control_background_ripple)
-                    }
+                    },
+                    model is FavoritesModel // Indicates that position information is needed
                 ) { id, favorite ->
                     model?.changeFavoriteStatus(id, favorite)
                 }
@@ -175,8 +181,14 @@
  */
 internal class ControlHolder(
     view: View,
+    val withPosition: Boolean,
     val favoriteCallback: ModelFavoriteChanger
 ) : Holder(view) {
+    private val favoriteStateDescription =
+        itemView.context.getString(R.string.accessibility_control_favorite)
+    private val notFavoriteStateDescription =
+        itemView.context.getString(R.string.accessibility_control_not_favorite)
+
     private val icon: ImageView = itemView.requireViewById(R.id.icon)
     private val title: TextView = itemView.requireViewById(R.id.title)
     private val subtitle: TextView = itemView.requireViewById(R.id.subtitle)
@@ -185,15 +197,38 @@
         visibility = View.VISIBLE
     }
 
+    private val accessibilityDelegate = ControlHolderAccessibilityDelegate(this::stateDescription)
+
+    init {
+        ViewCompat.setAccessibilityDelegate(itemView, accessibilityDelegate)
+    }
+
+    // Determine the stateDescription based on favorite state and maybe position
+    private fun stateDescription(favorite: Boolean): CharSequence? {
+        if (!favorite) {
+            return notFavoriteStateDescription
+        } else if (!withPosition) {
+            return favoriteStateDescription
+        } else {
+            val position = layoutPosition + 1
+            return itemView.context.getString(
+                R.string.accessibility_control_favorite_position, position)
+        }
+    }
+
     override fun bindData(wrapper: ElementWrapper) {
         wrapper as ControlInterface
         val renderInfo = getRenderInfo(wrapper.component, wrapper.deviceType)
         title.text = wrapper.title
         subtitle.text = wrapper.subtitle
-        favorite.isChecked = wrapper.favorite
-        removed.text = if (wrapper.removed) "Removed" else ""
+        updateFavorite(wrapper.favorite)
+        removed.text = if (wrapper.removed) {
+            itemView.context.getText(R.string.controls_removed)
+        } else {
+            ""
+        }
         itemView.setOnClickListener {
-            favorite.isChecked = !favorite.isChecked
+            updateFavorite(!favorite.isChecked)
             favoriteCallback(wrapper.controlId, favorite.isChecked)
         }
         applyRenderInfo(renderInfo)
@@ -201,6 +236,8 @@
 
     override fun updateFavorite(favorite: Boolean) {
         this.favorite.isChecked = favorite
+        accessibilityDelegate.isFavorite = favorite
+        itemView.stateDescription = stateDescription(favorite)
     }
 
     private fun getRenderInfo(
@@ -219,6 +256,36 @@
     }
 }
 
+private class ControlHolderAccessibilityDelegate(
+    val stateRetriever: (Boolean) -> CharSequence?
+) : AccessibilityDelegateCompat() {
+
+    var isFavorite = false
+
+    override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) {
+        super.onInitializeAccessibilityNodeInfo(host, info)
+
+        // Change the text for the double-tap action
+        val clickActionString = if (isFavorite) {
+            host.context.getString(R.string.accessibility_control_change_unfavorite)
+        } else {
+            host.context.getString(R.string.accessibility_control_change_favorite)
+        }
+        val click = AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+            AccessibilityNodeInfo.ACTION_CLICK,
+            // “favorite/unfavorite”
+            clickActionString)
+        info.addAction(click)
+
+        // Determine the stateDescription based on the holder information
+        info.stateDescription = stateRetriever(isFavorite)
+        // Remove the information at the end indicating row and column.
+        info.setCollectionItemInfo(null)
+
+        info.className = Switch::class.java.name
+    }
+}
+
 class MarginItemDecorator(
     private val topMargin: Int,
     private val sideMargins: Int
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index 4e9c550..3a4e82c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -191,7 +191,18 @@
 
         recyclerView.apply {
             this.adapter = adapter
-            layoutManager = GridLayoutManager(recyclerView.context, 2).apply {
+            layoutManager = object : GridLayoutManager(recyclerView.context, 2) {
+
+                // This will remove from the announcement the row corresponding to the divider,
+                // as it's not something that should be announced.
+                override fun getRowCountForAccessibility(
+                    recycler: RecyclerView.Recycler,
+                    state: RecyclerView.State
+                ): Int {
+                    val initial = super.getRowCountForAccessibility(recycler, state)
+                    return if (initial > 0) initial - 1 else initial
+                }
+            }.apply {
                 spanSizeLookup = adapter.spanSizeLookup
             }
             addItemDecoration(itemDecorator)
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 3f37861..03f8e66 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -206,7 +206,8 @@
     @VisibleForTesting
     protected final ArrayList<Action> mOverflowItems = new ArrayList<>();
 
-    private ActionsDialog mDialog;
+    @VisibleForTesting
+    protected ActionsDialog mDialog;
 
     private Action mSilentModeAction;
     private ToggleAction mAirplaneModeOn;
@@ -246,6 +247,9 @@
         @UiEvent(doc = "The global actions / power menu surface became visible on the screen.")
         GA_POWER_MENU_OPEN(337),
 
+        @UiEvent(doc = "The global actions / power menu surface was dismissed.")
+        GA_POWER_MENU_CLOSE(471),
+
         @UiEvent(doc = "The global actions bugreport button was pressed.")
         GA_BUGREPORT_PRESS(344),
 
@@ -360,10 +364,9 @@
             @Override
             public void onUnlockedChanged() {
                 if (mDialog != null) {
-                    boolean unlocked = keyguardStateController.isUnlocked()
-                            || keyguardStateController.canDismissLockScreen();
                     if (mDialog.mPanelController != null) {
-                        mDialog.mPanelController.onDeviceLockStateChanged(unlocked);
+                        mDialog.mPanelController.onDeviceLockStateChanged(
+                                !mKeyguardStateController.isUnlocked());
                     }
                     if (!mDialog.isShowingControls() && shouldShowControls()) {
                         mDialog.showControls(mControlsUiController);
@@ -1168,6 +1171,7 @@
         if (mDialog == dialog) {
             mDialog = null;
         }
+        mUiEventLogger.log(GlobalActionsEvent.GA_POWER_MENU_CLOSE);
         mWindowManagerFuncs.onGlobalActionsHidden();
         mLifecycle.setCurrentState(Lifecycle.State.DESTROYED);
     }
@@ -2378,9 +2382,7 @@
 
     @VisibleForTesting
     protected boolean shouldShowControls() {
-        boolean isUnlocked = mKeyguardStateController.isUnlocked()
-                || mKeyguardStateController.canDismissLockScreen();
-        return (isUnlocked || mShowLockScreenCardsAndControls)
+        return (mKeyguardStateController.isUnlocked() || mShowLockScreenCardsAndControls)
                 && mControlsUiController.getAvailable()
                 && !mControlsServiceInfos.isEmpty()
                 && mDeviceProvisioned;
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
index fa45ea1..1a53c28 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
@@ -134,7 +134,7 @@
     private void setupTexture(Bitmap bitmap) {
         final int[] tids = new int[1];
 
-        if (bitmap == null) {
+        if (bitmap == null || bitmap.isRecycled()) {
             Log.w(TAG, "setupTexture: invalid bitmap");
             return;
         }
@@ -146,16 +146,20 @@
             return;
         }
 
-        // Bind a named texture to a target.
-        glBindTexture(GL_TEXTURE_2D, tids[0]);
-        // Load the bitmap data and copy it over into the texture object that is currently bound.
-        GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
-        // Use bilinear texture filtering when minification.
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-        // Use bilinear texture filtering when magnification.
-        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
-        mTextureId = tids[0];
+        try {
+            // Bind a named texture to a target.
+            glBindTexture(GL_TEXTURE_2D, tids[0]);
+            // Load the bitmap data and copy it over into the texture object
+            // that is currently bound.
+            GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
+            // Use bilinear texture filtering when minification.
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+            // Use bilinear texture filtering when magnification.
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+            mTextureId = tids[0];
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "Failed uploading texture: " + e.getLocalizedMessage());
+        }
     }
 
     void useTexture() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
index 9c92b0a..d72c369 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
@@ -42,10 +42,10 @@
     val mediaCarousel: HorizontalScrollView
     private val mediaContent: ViewGroup
     private val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
-    private val visualStabilityCallback = ::reorderAllPlayers
+    private val visualStabilityCallback : VisualStabilityManager.Callback
     private var activeMediaIndex: Int = 0
+    private var needsReordering: Boolean = false
     private var scrollIntoCurrentMedia: Int = 0
-
     private var currentlyExpanded = true
         set(value) {
             if (field != value) {
@@ -75,6 +75,16 @@
         mediaCarousel = inflateMediaCarousel()
         mediaCarousel.setOnScrollChangeListener(scrollChangedListener)
         mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
+        visualStabilityCallback = VisualStabilityManager.Callback {
+            if (needsReordering) {
+                needsReordering = false
+                reorderAllPlayers()
+            }
+            // Let's reset our scroll position
+            mediaCarousel.scrollX = 0
+        }
+        visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback,
+                true /* persistent */)
         mediaManager.addListener(object : MediaDataManager.Listener {
             override fun onMediaDataLoaded(key: String, data: MediaData) {
                 updateView(key, data)
@@ -169,7 +179,7 @@
                 mediaContent.removeView(existingPlayer.view?.player)
                 mediaContent.addView(existingPlayer.view?.player, 0)
             } else {
-                visualStabilityManager.addReorderingAllowedCallback(visualStabilityCallback)
+                needsReordering = true
             }
         }
         existingPlayer.bind(data)
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index c8cf02a..10b04c0 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -302,13 +302,15 @@
     }
 
     private void showAutoSaverSuggestionNotification() {
+        final CharSequence message = mContext.getString(R.string.auto_saver_text);
         final Notification.Builder nb =
                 new Notification.Builder(mContext, NotificationChannels.HINTS)
                         .setSmallIcon(R.drawable.ic_power_saver)
                         .setWhen(0)
                         .setShowWhen(false)
                         .setContentTitle(mContext.getString(R.string.auto_saver_title))
-                        .setContentText(mContext.getString(R.string.auto_saver_text));
+                        .setStyle(new Notification.BigTextStyle().bigText(message))
+                        .setContentText(message);
         nb.setContentIntent(pendingBroadcast(ACTION_ENABLE_AUTO_SAVER));
         nb.setDeleteIntent(pendingBroadcast(ACTION_DISMISS_AUTO_SAVER_SUGGESTION));
         nb.addAction(0,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index b157f4b..aaff9ac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -152,15 +152,33 @@
     }
 
     @Override
+    public void fakeDragBy(float xOffset) {
+        try {
+            super.fakeDragBy(xOffset);
+            // Keep on drawing until the animation has finished.
+            postInvalidateOnAnimation();
+        } catch (NullPointerException e) {
+            Log.e(TAG, "FakeDragBy called before begin", e);
+            // If we were trying to fake drag, it means we just added a new tile to the last
+            // page, so animate there.
+            final int lastPageNumber = mPages.size() - 1;
+            post(() -> {
+                setCurrentItem(lastPageNumber, true);
+                if (mBounceAnimatorSet != null) {
+                    mBounceAnimatorSet.start();
+                }
+                setOffscreenPageLimit(1);
+            });
+        }
+    }
+
+    @Override
     public void computeScroll() {
         if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
             if (!isFakeDragging()) {
                 beginFakeDrag();
             }
             fakeDragBy(getScrollX() - mScroller.getCurrX());
-            // Keep on drawing until the animation has finished.
-            postInvalidateOnAnimation();
-            return;
         } else if (isFakeDragging()) {
             endFakeDrag();
             mBounceAnimatorSet.start();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 54a928d..4008918 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -339,13 +339,18 @@
         changeTileSpecs(tileSpecs-> !tileSpecs.contains(spec) && tileSpecs.add(spec));
     }
 
+    private void saveTilesToSettings(List<String> tileSpecs) {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING,
+                TextUtils.join(",", tileSpecs), null /* tag */,
+                false /* default */, mCurrentUser, true /* overrideable by restore */);
+    }
+
     private void changeTileSpecs(Predicate<List<String>> changeFunction) {
         final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(),
-            TILES_SETTING, ActivityManager.getCurrentUser());
+                TILES_SETTING, mCurrentUser);
         final List<String> tileSpecs = loadTileSpecs(mContext, setting);
         if (changeFunction.test(tileSpecs)) {
-            Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING,
-                TextUtils.join(",", tileSpecs), ActivityManager.getCurrentUser());
+            saveTilesToSettings(tileSpecs);
         }
     }
 
@@ -375,7 +380,7 @@
                 Intent intent = new Intent().setComponent(component);
                 TileLifecycleManager lifecycleManager = new TileLifecycleManager(new Handler(),
                         mContext, mServices, new Tile(), intent,
-                        new UserHandle(ActivityManager.getCurrentUser()),
+                        new UserHandle(mCurrentUser),
                         mBroadcastDispatcher);
                 lifecycleManager.onStopListening();
                 lifecycleManager.onTileRemoved();
@@ -384,8 +389,7 @@
             }
         }
         if (DEBUG) Log.d(TAG, "saveCurrentTiles " + newTiles);
-        Secure.putStringForUser(getContext().getContentResolver(), QSTileHost.TILES_SETTING,
-                TextUtils.join(",", newTiles), ActivityManager.getCurrentUser());
+        saveTilesToSettings(newTiles);
     }
 
     public QSTile createTile(String tileSpec) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 02a7aca..db33c79 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -592,13 +592,11 @@
         removeDivider();
         addDivider(configuration);
 
-        if (mView != null) {
-            if (mMinimized) {
-                mView.setMinimizedDockStack(true, mHomeStackResizable);
-                updateTouchable();
-            }
-            mView.setHidden(isDividerHidden);
+        if (mMinimized) {
+            mView.setMinimizedDockStack(true, mHomeStackResizable);
+            updateTouchable();
         }
+        mView.setHidden(isDividerHidden);
     }
 
     void onTaskVanished() {
@@ -610,7 +608,7 @@
                 mContext.getDisplayId()).getResources().getConfiguration()));
     }
 
-    void updateVisibility(final boolean visible) {
+    private void updateVisibility(final boolean visible) {
         if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
         if (mVisible != visible) {
             mVisible = visible;
@@ -639,6 +637,7 @@
     void onSplitDismissed() {
         mMinimized = false;
         updateVisibility(false /* visible */);
+        removeDivider();
     }
 
     /** Switch to minimized state if appropriate */
@@ -788,6 +787,8 @@
     }
 
     void startEnterSplit() {
+        update(mDisplayController.getDisplayContext(
+                mContext.getDisplayId()).getResources().getConfiguration());
         // Set resizable directly here because applyEnterSplit already resizes home stack.
         mHomeStackResizable = WindowManagerProxy.applyEnterSplit(mSplits, mSplitLayout);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index e7f2618..765f85a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -195,13 +195,15 @@
                 boolean wasChildInGroup = ent.isChildInGroup();
                 if (isChildInGroup && !wasChildInGroup) {
                     isChildInGroup = wasChildInGroup;
-                    mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager);
+                    mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager,
+                            false /* persistent */);
                 } else if (!isChildInGroup && wasChildInGroup) {
                     // We allow grouping changes if the group was collapsed
                     if (mGroupManager.isLogicalGroupExpanded(ent.getSbn())) {
                         isChildInGroup = wasChildInGroup;
                         parent = ent.getRow().getNotificationParent().getEntry();
-                        mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager);
+                        mVisualStabilityManager.addGroupChangesAllowedCallback(mEntryManager,
+                                false /* persistent */);
                     }
                 }
             }
@@ -286,7 +288,8 @@
                 if (mVisualStabilityManager.canReorderNotification(targetChild)) {
                     mListContainer.changeViewPosition(targetChild, i);
                 } else {
-                    mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager);
+                    mVisualStabilityManager.addReorderingAllowedCallback(mEntryManager,
+                            false  /* persistent */);
                 }
             }
             j++;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 02c98ae..88f148b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -121,14 +121,14 @@
     }
 
     override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
-        return canHandleMotionEvent() && startExpansion(event)
+        return maybeStartExpansion(event)
     }
 
-    private fun canHandleMotionEvent(): Boolean {
-        return !wakeUpCoordinator.canShowPulsingHuns || qsExpanded || bouncerShowing
-    }
-
-    private fun startExpansion(event: MotionEvent): Boolean {
+    private fun maybeStartExpansion(event: MotionEvent): Boolean {
+        if (!wakeUpCoordinator.canShowPulsingHuns || qsExpanded ||
+                bouncerShowing) {
+            return false
+        }
         if (velocityTracker == null) {
             velocityTracker = VelocityTracker.obtain()
         }
@@ -177,14 +177,9 @@
     }
 
     override fun onTouchEvent(event: MotionEvent): Boolean {
-        if (!canHandleMotionEvent()) {
-            return false
+        if (!isExpanding) {
+            return maybeStartExpansion(event)
         }
-
-        if (!isExpanding || event.actionMasked == MotionEvent.ACTION_DOWN) {
-            return startExpansion(event)
-        }
-
         velocityTracker!!.addMovement(event)
         val y = event.y
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
index e2b01ff..d7b391f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
@@ -22,6 +22,7 @@
 import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
@@ -52,12 +53,14 @@
     fun getNotificationBuckets(): IntArray {
         return when {
             isFilteringEnabled() && isMediaControlsEnabled() ->
-                intArrayOf(BUCKET_HEADS_UP, BUCKET_MEDIA_CONTROLS, BUCKET_PEOPLE, BUCKET_ALERTING,
-                    BUCKET_SILENT)
+                intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_MEDIA_CONTROLS,
+                        BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT)
             !isFilteringEnabled() && isMediaControlsEnabled() ->
-                intArrayOf(BUCKET_HEADS_UP, BUCKET_MEDIA_CONTROLS, BUCKET_ALERTING, BUCKET_SILENT)
+                intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_MEDIA_CONTROLS,
+                        BUCKET_ALERTING, BUCKET_SILENT)
             isFilteringEnabled() && !isMediaControlsEnabled() ->
-                intArrayOf(BUCKET_HEADS_UP, BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT)
+                intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_PEOPLE,
+                        BUCKET_ALERTING, BUCKET_SILENT)
             NotificationUtils.useNewInterruptionModel(context) ->
                 intArrayOf(BUCKET_ALERTING, BUCKET_SILENT)
             else ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
index 7ac5995..8341c02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/VisualStabilityManager.java
@@ -43,7 +43,9 @@
     private static final long TEMPORARY_REORDERING_ALLOWED_DURATION = 1000;
 
     private final ArrayList<Callback> mReorderingAllowedCallbacks = new ArrayList<>();
+    private final ArraySet<Callback> mPersistentReorderingCallbacks = new ArraySet<>();
     private final ArrayList<Callback> mGroupChangesAllowedCallbacks = new ArrayList<>();
+    private final ArraySet<Callback> mPersistentGroupCallbacks = new ArraySet<>();
     private final Handler mHandler;
 
     private boolean mPanelExpanded;
@@ -85,8 +87,15 @@
 
     /**
      * Add a callback to invoke when reordering is allowed again.
+     *
+     * @param callback the callback to add
+     * @param persistent {@code true} if this callback should this callback be persisted, otherwise
+     *                               it will be removed after a single invocation
      */
-    public void addReorderingAllowedCallback(Callback callback) {
+    public void addReorderingAllowedCallback(Callback callback, boolean persistent) {
+        if (persistent) {
+            mPersistentReorderingCallbacks.add(callback);
+        }
         if (mReorderingAllowedCallbacks.contains(callback)) {
             return;
         }
@@ -95,8 +104,15 @@
 
     /**
      * Add a callback to invoke when group changes are allowed again.
+     *
+     * @param callback the callback to add
+     * @param persistent {@code true} if this callback should this callback be persisted, otherwise
+     *                               it will be removed after a single invocation
      */
-    public void addGroupChangesAllowedCallback(Callback callback) {
+    public void addGroupChangesAllowedCallback(Callback callback, boolean persistent) {
+        if (persistent) {
+            mPersistentGroupCallbacks.add(callback);
+        }
         if (mGroupChangesAllowedCallbacks.contains(callback)) {
             return;
         }
@@ -136,21 +152,26 @@
         boolean changedToTrue = reorderingAllowed && !mReorderingAllowed;
         mReorderingAllowed = reorderingAllowed;
         if (changedToTrue) {
-            notifyChangeAllowed(mReorderingAllowedCallbacks);
+            notifyChangeAllowed(mReorderingAllowedCallbacks, mPersistentReorderingCallbacks);
         }
         boolean groupChangesAllowed = (!mScreenOn || !mPanelExpanded) && !mPulsing;
         changedToTrue = groupChangesAllowed && !mGroupChangedAllowed;
         mGroupChangedAllowed = groupChangesAllowed;
         if (changedToTrue) {
-            notifyChangeAllowed(mGroupChangesAllowedCallbacks);
+            notifyChangeAllowed(mGroupChangesAllowedCallbacks, mPersistentGroupCallbacks);
         }
     }
 
-    private void notifyChangeAllowed(ArrayList<Callback> callbacks) {
+    private void notifyChangeAllowed(ArrayList<Callback> callbacks,
+            ArraySet<Callback> persistentCallbacks) {
         for (int i = 0; i < callbacks.size(); i++) {
-            callbacks.get(i).onChangeAllowed();
+            Callback callback = callbacks.get(i);
+            callback.onChangeAllowed();
+            if (!persistentCallbacks.contains(callback)) {
+                callbacks.remove(callback);
+                i--;
+            }
         }
-        callbacks.clear();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index c78370e..9ac4229 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
 import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
@@ -162,6 +163,8 @@
         val isMedia = isImportantMedia(entry)
         val isSystemMax = entry.isSystemMax()
         return when {
+            entry.sbn.notification.isForegroundService && entry.sbn.notification.isColorized ->
+                BUCKET_FOREGROUND_SERVICE
             usePeopleFiltering && entry.getPeopleNotificationType() != TYPE_NON_PERSON ->
                 BUCKET_PEOPLE
             isHeadsUp || isMedia || isSystemMax || entry.isHighPriority() ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
index cc5de65..66c07bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
@@ -135,10 +135,13 @@
     }
 
     private void bindActions() {
+        final OnClickListener settingsOnClickListener = getSettingsOnClickListener();
         final View settingsButton = findViewById(R.id.info);
-        settingsButton.setOnClickListener(getSettingsOnClickListener());
+        settingsButton.setOnClickListener(settingsOnClickListener);
         settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
 
+        findViewById(R.id.settings_link).setOnClickListener(settingsOnClickListener);
+
         TextView msg = findViewById(R.id.non_configurable_text);
         msg.setText(getResources().getString(R.string.no_shortcut, mAppName));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index c9b1318..99691b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -463,7 +463,8 @@
                     mAttachedChildren.add(i, desiredChild);
                     result = true;
                 } else {
-                    visualStabilityManager.addReorderingAllowedCallback(callback);
+                    visualStabilityManager.addReorderingAllowedCallback(callback,
+                            false /* persistent */);
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index dcf30ca..b5ba3a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -29,12 +29,10 @@
 import android.provider.Settings;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
 import com.android.systemui.media.KeyguardMediaController;
-import com.android.systemui.media.MediaHierarchyManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -329,8 +327,6 @@
         boolean peopleNotifsPresent = false;
 
         int currentMediaControlsIdx = -1;
-        // Currently, just putting media controls in the front and incrementing the position based
-        // on the number of heads-up notifs.
         int mediaControlsTarget = usingMediaControls ? 0 : -1;
         int currentIncomingHeaderIdx = -1;
         int incomingHeaderTarget = -1;
@@ -408,6 +404,11 @@
                         mediaControlsTarget++;
                     }
                     break;
+                case BUCKET_FOREGROUND_SERVICE:
+                    if (mediaControlsTarget != -1) {
+                        mediaControlsTarget++;
+                    }
+                    break;
                 case BUCKET_PEOPLE:
                     mLogger.logPosition(i, "Conversation");
                     peopleNotifsPresent = true;
@@ -488,7 +489,8 @@
         adjustHeaderVisibilityAndPosition(
                 peopleHeaderTarget, mPeopleHubView, currentPeopleHeaderIdx);
         adjustViewPosition(mediaControlsTarget, mMediaControlsView, currentMediaControlsIdx);
-        adjustViewPosition(incomingHeaderTarget, mIncomingHeader, currentIncomingHeaderIdx);
+        adjustHeaderVisibilityAndPosition(incomingHeaderTarget, mIncomingHeader,
+                currentIncomingHeaderIdx);
 
 
         mLogger.logStr("Final order:");
@@ -508,45 +510,29 @@
 
     private void adjustHeaderVisibilityAndPosition(
             int targetPosition, StackScrollerDecorView header, int currentPosition) {
-        if (targetPosition == -1) {
-            if (currentPosition != -1) {
-                mParent.removeView(header);
-            }
-        } else {
-            if (currentPosition == -1) {
-                // If the header is animating away, it will still have a parent, so detach it first
-                // TODO: We should really cancel the active animations here. This will happen
-                // automatically when the view's intro animation starts, but it's a fragile link.
-                if (header.getTransientContainer() != null) {
-                    header.getTransientContainer().removeTransientView(header);
-                    header.setTransientContainer(null);
-                }
-                header.setContentVisible(true);
-                mParent.addView(header, targetPosition);
-            } else {
-                mParent.changeViewPosition(header, targetPosition);
-            }
+        adjustViewPosition(targetPosition, header, currentPosition);
+        if (targetPosition != -1 && currentPosition == -1) {
+            header.setContentVisible(true);
         }
     }
 
-    private void adjustViewPosition(int targetPosition, ExpandableView header,
-            int currentPosition) {
+    private void adjustViewPosition(int targetPosition, ExpandableView view, int currentPosition) {
         if (targetPosition == -1) {
             if (currentPosition != -1) {
-                mParent.removeView(header);
+                mParent.removeView(view);
             }
         } else {
             if (currentPosition == -1) {
                 // If the header is animating away, it will still have a parent, so detach it first
                 // TODO: We should really cancel the active animations here. This will happen
                 // automatically when the view's intro animation starts, but it's a fragile link.
-                if (header.getTransientContainer() != null) {
-                    header.getTransientContainer().removeTransientView(header);
-                    header.setTransientContainer(null);
+                if (view.getTransientContainer() != null) {
+                    view.getTransientContainer().removeTransientView(view);
+                    view.setTransientContainer(null);
                 }
-                mParent.addView(header, targetPosition);
+                mParent.addView(view, targetPosition);
             } else {
-                mParent.changeViewPosition(header, targetPosition);
+                mParent.changeViewPosition(view, targetPosition);
             }
         }
     }
@@ -641,6 +627,11 @@
     }
 
     @VisibleForTesting
+    ExpandableView getIncomingHeaderView() {
+        return mIncomingHeader;
+    }
+
+    @VisibleForTesting
     void setPeopleHubVisible(boolean visible) {
         mPeopleHubVisible = visible;
     }
@@ -685,6 +676,7 @@
     @Retention(SOURCE)
     @IntDef(prefix = { "BUCKET_" }, value = {
             BUCKET_HEADS_UP,
+            BUCKET_FOREGROUND_SERVICE,
             BUCKET_MEDIA_CONTROLS,
             BUCKET_PEOPLE,
             BUCKET_ALERTING,
@@ -692,8 +684,9 @@
     })
     public @interface PriorityBucket {}
     public static final int BUCKET_HEADS_UP = 0;
-    public static final int BUCKET_MEDIA_CONTROLS = 1;
-    public static final int BUCKET_PEOPLE = 2;
-    public static final int BUCKET_ALERTING = 3;
-    public static final int BUCKET_SILENT = 4;
+    public static final int BUCKET_FOREGROUND_SERVICE = 1;
+    public static final int BUCKET_MEDIA_CONTROLS = 2;
+    public static final int BUCKET_PEOPLE = 3;
+    public static final int BUCKET_ALERTING = 4;
+    public static final int BUCKET_SILENT = 5;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 6b0df95..3dcf7ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -439,7 +439,8 @@
                         // time out anyway
                         && !entry.showingPulsing()) {
                     mEntriesToRemoveWhenReorderingAllowed.add(entry);
-                    mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManagerPhone.this);
+                    mVisualStabilityManager.addReorderingAllowedCallback(HeadsUpManagerPhone.this,
+                            false  /* persistent */);
                 } else if (mTrackingHeadsUp) {
                     mEntriesToRemoveAfterExpand.add(entry);
                 } else if (mIsAutoHeadsUp && mStatusBarState == StatusBarState.KEYGUARD) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
index 2e4a929d..926c1bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowController.java
@@ -321,8 +321,7 @@
                 || state.mPanelVisible || state.mKeyguardFadingAway || state.mBouncerShowing
                 || state.mHeadsUpShowing
                 || state.mScrimsVisibility != ScrimController.TRANSPARENT)
-                || state.mBackgroundBlurRadius > 0
-                || state.mLaunchingActivity;
+                || state.mBackgroundBlurRadius > 0;
     }
 
     private void applyFitsSystemWindows(State state) {
@@ -486,11 +485,6 @@
         apply(mCurrentState);
     }
 
-    void setLaunchingActivity(boolean launching) {
-        mCurrentState.mLaunchingActivity = launching;
-        apply(mCurrentState);
-    }
-
     public void setScrimsVisibility(int scrimsVisibility) {
         mCurrentState.mScrimsVisibility = scrimsVisibility;
         apply(mCurrentState);
@@ -651,7 +645,6 @@
         boolean mForceCollapsed;
         boolean mForceDozeBrightness;
         boolean mForceUserActivity;
-        boolean mLaunchingActivity;
         boolean mBackdropShowing;
         boolean mWallpaperSupportsAmbientMode;
         boolean mNotTouchable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 0d25898..596a607 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -93,7 +93,6 @@
     private PhoneStatusBarView mStatusBarView;
     private PhoneStatusBarTransitions mBarTransitions;
     private StatusBar mService;
-    private NotificationShadeWindowController mNotificationShadeWindowController;
     private DragDownHelper mDragDownHelper;
     private boolean mDoubleTapEnabled;
     private boolean mSingleTapEnabled;
@@ -431,14 +430,10 @@
 
     public void setExpandAnimationPending(boolean pending) {
         mExpandAnimationPending = pending;
-        mNotificationShadeWindowController
-                .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
     }
 
     public void setExpandAnimationRunning(boolean running) {
         mExpandAnimationRunning = running;
-        mNotificationShadeWindowController
-                .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
     }
 
     public void cancelExpandHelper() {
@@ -461,9 +456,8 @@
         }
     }
 
-    public void setService(StatusBar statusBar, NotificationShadeWindowController controller) {
+    public void setService(StatusBar statusBar) {
         mService = statusBar;
-        mNotificationShadeWindowController = controller;
     }
 
     @VisibleForTesting
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 dd54a3d..bbf83bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1001,7 +1001,7 @@
         updateTheme();
 
         inflateStatusBarWindow();
-        mNotificationShadeWindowViewController.setService(this, mNotificationShadeWindowController);
+        mNotificationShadeWindowViewController.setService(this);
         mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
 
         // TODO: Deal with the ugliness that comes from having some of the statusbar broken out
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 442c7ea..b6e72226 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -369,16 +369,24 @@
                     R.string.ext_media_new_notification_message, disk.getDescription());
 
             final PendingIntent initIntent = buildInitPendingIntent(vol);
-            return buildNotificationBuilder(vol, title, text)
-                    .addAction(new Action(R.drawable.ic_settings_24dp,
-                            mContext.getString(R.string.ext_media_init_action), initIntent))
-                    .addAction(new Action(R.drawable.ic_eject_24dp,
-                            mContext.getString(R.string.ext_media_unmount_action),
-                            buildUnmountPendingIntent(vol)))
-                    .setContentIntent(initIntent)
-                    .setDeleteIntent(buildSnoozeIntent(vol.getFsUuid()))
-                    .build();
+            final PendingIntent unmountIntent = buildUnmountPendingIntent(vol);
 
+            if (isAutomotive()) {
+                return buildNotificationBuilder(vol, title, text)
+                        .setContentIntent(unmountIntent)
+                        .setDeleteIntent(buildSnoozeIntent(vol.getFsUuid()))
+                        .build();
+            } else {
+                return buildNotificationBuilder(vol, title, text)
+                        .addAction(new Action(R.drawable.ic_settings_24dp,
+                                mContext.getString(R.string.ext_media_init_action), initIntent))
+                        .addAction(new Action(R.drawable.ic_eject_24dp,
+                                mContext.getString(R.string.ext_media_unmount_action),
+                                unmountIntent))
+                        .setContentIntent(initIntent)
+                        .setDeleteIntent(buildSnoozeIntent(vol.getFsUuid()))
+                        .build();
+            }
         } else {
             final CharSequence title = disk.getDescription();
             final CharSequence text = mContext.getString(
@@ -427,9 +435,15 @@
                 R.string.ext_media_unmountable_notification_title, disk.getDescription());
         final CharSequence text = mContext.getString(
                 R.string.ext_media_unmountable_notification_message, disk.getDescription());
+        PendingIntent action;
+        if (isAutomotive()) {
+            action = buildUnmountPendingIntent(vol);
+        } else {
+            action = buildInitPendingIntent(vol);
+        }
 
         return buildNotificationBuilder(vol, title, text)
-                .setContentIntent(buildInitPendingIntent(vol))
+                .setContentIntent(action)
                 .setCategory(Notification.CATEGORY_ERROR)
                 .build();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
index 93f45c5..21f67ae 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
@@ -125,7 +125,7 @@
      */
     public void removeView(View view) {
         SurfaceControlViewHost root = mViewRoots.remove(view);
-        root.die();
+        root.release();
     }
 
     /**
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index 870010a..aa4122f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -134,6 +134,7 @@
         mDependency.injectMockDependency(WakefulnessLifecycle.class);
         mDependency.injectTestDependency(Dependency.MAIN_HANDLER,
                 new Handler(mTestableLooper.getLooper()));
+        mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
         mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
 
         doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor)
@@ -150,6 +151,7 @@
         // This should not start listening on any of the real dependencies but will test that
         // callbacks in mKeyguardUpdateMonitor are done in the mTestableLooper thread
         mCarrierTextController.setListening(mCarrierTextCallback);
+        mTestableLooper.processAllMessages();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 64acdcc..ee7733a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -156,13 +156,20 @@
     }
 
     @Test
-    public void testShouldLogVisibility() {
+    public void testShouldLogShow() {
         mGlobalActionsDialog.onShow(null);
         mTestableLooper.processAllMessages();
         verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_OPEN);
     }
 
     @Test
+    public void testShouldLogDismiss() {
+        mGlobalActionsDialog.onDismiss(mGlobalActionsDialog.mDialog);
+        mTestableLooper.processAllMessages();
+        verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_POWER_MENU_CLOSE);
+    }
+
+    @Test
     public void testShouldLogBugreportPress() throws InterruptedException {
         GlobalActionsDialog.BugReportAction bugReportAction =
                 mGlobalActionsDialog.makeBugReportActionForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
index ca7a5db..8948fd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt
@@ -74,7 +74,7 @@
                 DeviceConfig.NAMESPACE_SYSTEMUI, NOTIFICATIONS_USE_PEOPLE_FILTERING, "true", false)
 
         assertTrue("People filtering should be enabled", manager!!.isFilteringEnabled())
-        assertTrue("Expecting 4 buckets when people filtering is enabled",
-                manager!!.getNumberOfBuckets() == 4)
+        assertTrue("Expecting 5 buckets when people filtering is enabled",
+                manager!!.getNumberOfBuckets() == 5)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
index 3d06c57..ea1b498 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/VisualStabilityManagerTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -108,7 +109,7 @@
     public void testCallBackCalledScreenOn() {
         mVisualStabilityManager.setPanelExpanded(true);
         mVisualStabilityManager.setScreenOn(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
         mVisualStabilityManager.setScreenOn(false);
         verify(mCallback).onChangeAllowed();
     }
@@ -117,7 +118,7 @@
     public void testCallBackCalledPanelExpanded() {
         mVisualStabilityManager.setPanelExpanded(true);
         mVisualStabilityManager.setScreenOn(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
         mVisualStabilityManager.setPanelExpanded(false);
         verify(mCallback).onChangeAllowed();
     }
@@ -126,7 +127,7 @@
     public void testCallBackExactlyOnce() {
         mVisualStabilityManager.setPanelExpanded(true);
         mVisualStabilityManager.setScreenOn(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
         mVisualStabilityManager.setScreenOn(false);
         mVisualStabilityManager.setScreenOn(true);
         mVisualStabilityManager.setScreenOn(false);
@@ -134,6 +135,17 @@
     }
 
     @Test
+    public void testCallBackCalledContinuouslyWhenRequested() {
+        mVisualStabilityManager.setPanelExpanded(true);
+        mVisualStabilityManager.setScreenOn(true);
+        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, true  /* persistent */);
+        mVisualStabilityManager.setScreenOn(false);
+        mVisualStabilityManager.setScreenOn(true);
+        mVisualStabilityManager.setScreenOn(false);
+        verify(mCallback, times(2)).onChangeAllowed();
+    }
+
+    @Test
     public void testAddedCanReorder() {
         mVisualStabilityManager.setPanelExpanded(true);
         mVisualStabilityManager.setScreenOn(true);
@@ -188,7 +200,7 @@
     @Test
     public void testCallBackCalled_Pulsing() {
         mVisualStabilityManager.setPulsing(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
         mVisualStabilityManager.setPulsing(false);
         verify(mCallback).onChangeAllowed();
     }
@@ -198,7 +210,7 @@
         // GIVEN having the panel open (which would block reordering)
         mVisualStabilityManager.setScreenOn(true);
         mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
 
         // WHEN we temprarily allow reordering
         mVisualStabilityManager.temporarilyAllowReordering();
@@ -212,7 +224,7 @@
     public void testTemporarilyAllowReorderingDoesntOverridePulsing() {
         // GIVEN we are in a pulsing state
         mVisualStabilityManager.setPulsing(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
 
         // WHEN we temprarily allow reordering
         mVisualStabilityManager.temporarilyAllowReordering();
@@ -227,7 +239,7 @@
         // GIVEN having the panel open (which would block reordering)
         mVisualStabilityManager.setScreenOn(true);
         mVisualStabilityManager.setPanelExpanded(true);
-        mVisualStabilityManager.addReorderingAllowedCallback(mCallback);
+        mVisualStabilityManager.addReorderingAllowedCallback(mCallback, false  /* persistent */);
 
         // WHEN we temprarily allow reordering and then wait until the window expires
         mVisualStabilityManager.temporarilyAllowReordering();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
index 1bfebfb..f21b1a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java
@@ -356,6 +356,30 @@
     }
 
     @Test
+    public void testBindNotification_SetsOnClickListenerForSettings_mainText() {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mInfo.bindNotification(
+                mMockPackageManager,
+                mMockINotificationManager,
+                mChannelEditorDialogController,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mNotificationChannelSet,
+                mEntry,
+                (View v, NotificationChannel c, int appUid) -> {
+                    assertEquals(mNotificationChannel, c);
+                    latch.countDown();
+                },
+                true,
+                false);
+
+        final View settingsButton = mInfo.findViewById(R.id.settings_link);
+        settingsButton.performClick();
+        // Verify that listener was triggered.
+        assertEquals(0, latch.getCount());
+    }
+
+    @Test
     public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() {
         mInfo.bindNotification(
                 mMockPackageManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 0b86a78..546bce8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -19,15 +19,19 @@
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_FOREGROUND_SERVICE;
 import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP;
 import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE;
 import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -62,6 +66,9 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -84,10 +91,23 @@
 
     @Before
     public void setUp() {
-        when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(2);
-        when(mNotificationRowComponent.getActivatableNotificationViewController()).thenReturn(
-                mActivatableNotificationViewController
-        );
+        when(mSectionsFeatureManager.getNumberOfBuckets()).thenAnswer(
+                invocation -> {
+                    int count = 2;
+                    if (mSectionsFeatureManager.isFilteringEnabled()) {
+                        count = 5;
+                    }
+                    if (mSectionsFeatureManager.isMediaControlsEnabled()) {
+                        if (!mSectionsFeatureManager.isFilteringEnabled()) {
+                            count = 5;
+                        } else {
+                            count += 1;
+                        }
+                    }
+                    return count;
+                });
+        when(mNotificationRowComponent.getActivatableNotificationViewController())
+                .thenReturn(mActivatableNotificationViewController);
         mSectionsManager =
                 new NotificationSectionsManager(
                         mActivityStarterDelegate,
@@ -104,6 +124,7 @@
         mSectionsManager.initialize(mNssl, LayoutInflater.from(mContext));
         when(mNssl.indexOfChild(any(View.class))).thenReturn(-1);
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
+
     }
 
     @Test(expected =  IllegalStateException.class)
@@ -339,6 +360,58 @@
     }
 
     @Test
+    public void testPeopleFiltering_HunWhilePeopleVisible() {
+        enablePeopleFiltering();
+
+        setupMockStack(
+                ChildType.PEOPLE_HEADER,
+                ChildType.HEADS_UP,
+                ChildType.PERSON,
+                ChildType.ALERTING_HEADER,
+                ChildType.GENTLE_HEADER,
+                ChildType.GENTLE
+        );
+        mSectionsManager.updateSectionBoundaries();
+
+        verifyMockStack(
+                ChildType.INCOMING_HEADER,
+                ChildType.HEADS_UP,
+                ChildType.PEOPLE_HEADER,
+                ChildType.PERSON,
+                ChildType.GENTLE_HEADER,
+                ChildType.GENTLE
+        );
+    }
+
+    @Test
+    public void testPeopleFiltering_Fsn() {
+        enablePeopleFiltering();
+
+        setupMockStack(
+                ChildType.INCOMING_HEADER,
+                ChildType.HEADS_UP,
+                ChildType.PEOPLE_HEADER,
+                ChildType.FSN,
+                ChildType.PERSON,
+                ChildType.ALERTING,
+                ChildType.GENTLE
+        );
+        mSectionsManager.updateSectionBoundaries();
+
+        verifyMockStack(
+                ChildType.INCOMING_HEADER,
+                ChildType.HEADS_UP,
+                ChildType.FSN,
+                ChildType.PEOPLE_HEADER,
+                ChildType.PERSON,
+                ChildType.ALERTING_HEADER,
+                ChildType.ALERTING,
+                ChildType.GENTLE_HEADER,
+                ChildType.GENTLE
+        );
+    }
+
+    @Test
     public void testMediaControls_AddWhenEnterKeyguard() {
         enableMediaControls();
 
@@ -358,30 +431,28 @@
         enableMediaControls();
 
         // GIVEN a stack that doesn't include media controls but includes HEADS_UP
-        setStackState(ChildType.HEADS_UP, ChildType.ALERTING, ChildType.GENTLE_HEADER,
+        setupMockStack(ChildType.HEADS_UP, ChildType.ALERTING, ChildType.GENTLE_HEADER,
                 ChildType.GENTLE);
 
         // WHEN we go back to the keyguard
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD);
         mSectionsManager.updateSectionBoundaries();
 
-        // Then the media controls are added after HEADS_UP
-        verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 1);
+        verifyMockStack(ChildType.HEADS_UP, ChildType.MEDIA_CONTROLS, ChildType.ALERTING,
+                ChildType.GENTLE);
     }
 
     private void enablePeopleFiltering() {
         when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true);
-        when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4);
     }
 
     private void enableMediaControls() {
         when(mSectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true);
-        when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4);
     }
 
     private enum ChildType {
-        MEDIA_CONTROLS, PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP, PERSON, ALERTING,
-            GENTLE, OTHER
+        INCOMING_HEADER, MEDIA_CONTROLS, PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP,
+        FSN, PERSON, ALERTING, GENTLE, OTHER
     }
 
     private void setStackState(ChildType... children) {
@@ -389,6 +460,9 @@
         for (int i = 0; i < children.length; i++) {
             View child;
             switch (children[i]) {
+                case INCOMING_HEADER:
+                    child = mSectionsManager.getIncomingHeaderView();
+                    break;
                 case MEDIA_CONTROLS:
                     child = mSectionsManager.getMediaControlsView();
                     break;
@@ -404,6 +478,9 @@
                 case HEADS_UP:
                     child = mockNotification(BUCKET_HEADS_UP);
                     break;
+                case FSN:
+                    child = mockNotification(BUCKET_FOREGROUND_SERVICE);
+                    break;
                 case PERSON:
                     child = mockNotification(BUCKET_PEOPLE);
                     break;
@@ -434,4 +511,127 @@
         when(notifRow.getParent()).thenReturn(mNssl);
         return notifRow;
     }
+
+    private void verifyMockStack(ChildType... expected) {
+        final List<ChildType> actual = new ArrayList<>();
+        int childCount = mNssl.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = mNssl.getChildAt(i);
+            if (child == mSectionsManager.getIncomingHeaderView()) {
+                actual.add(ChildType.INCOMING_HEADER);
+                continue;
+            }
+            if (child == mSectionsManager.getMediaControlsView()) {
+                actual.add(ChildType.MEDIA_CONTROLS);
+                continue;
+            }
+            if (child == mSectionsManager.getPeopleHeaderView()) {
+                actual.add(ChildType.PEOPLE_HEADER);
+                continue;
+            }
+            if (child == mSectionsManager.getAlertingHeaderView()) {
+                actual.add(ChildType.ALERTING_HEADER);
+                continue;
+            }
+            if (child == mSectionsManager.getGentleHeaderView()) {
+                actual.add(ChildType.GENTLE_HEADER);
+                continue;
+            }
+            if (child instanceof ExpandableNotificationRow) {
+                switch (((ExpandableNotificationRow) child).getEntry().getBucket()) {
+                    case BUCKET_HEADS_UP:
+                        actual.add(ChildType.HEADS_UP);
+                        break;
+                    case BUCKET_FOREGROUND_SERVICE:
+                        actual.add(ChildType.FSN);
+                        break;
+                    case BUCKET_PEOPLE:
+                        actual.add(ChildType.PERSON);
+                        break;
+                    case BUCKET_ALERTING:
+                        actual.add(ChildType.ALERTING);
+                        break;
+                    case BUCKET_SILENT:
+                        actual.add(ChildType.GENTLE);
+                        break;
+                    default:
+                        actual.add(ChildType.OTHER);
+                        break;
+                }
+                continue;
+            }
+            actual.add(ChildType.OTHER);
+        }
+        assertThat(actual).containsExactly((Object[]) expected).inOrder();
+    }
+
+    private void setupMockStack(ChildType... childTypes) {
+        final List<View> children = new ArrayList<>();
+        when(mNssl.getChildCount()).thenAnswer(invocation -> children.size());
+        when(mNssl.getChildAt(anyInt()))
+                .thenAnswer(invocation -> children.get(invocation.getArgument(0)));
+        when(mNssl.indexOfChild(any()))
+                .thenAnswer(invocation -> children.indexOf(invocation.getArgument(0)));
+        doAnswer(invocation -> {
+            View child = invocation.getArgument(0);
+            int index = invocation.getArgument(1);
+            children.add(index, child);
+            return null;
+        }).when(mNssl).addView(any(), anyInt());
+        doAnswer(invocation -> {
+            View child = invocation.getArgument(0);
+            children.remove(child);
+            return null;
+        }).when(mNssl).removeView(any());
+        doAnswer(invocation -> {
+            View child = invocation.getArgument(0);
+            int newIndex = invocation.getArgument(1);
+            children.remove(child);
+            children.add(newIndex, child);
+            return null;
+        }).when(mNssl).changeViewPosition(any(), anyInt());
+        for (ChildType childType : childTypes) {
+            View child;
+            switch (childType) {
+                case INCOMING_HEADER:
+                    child = mSectionsManager.getIncomingHeaderView();
+                    break;
+                case MEDIA_CONTROLS:
+                    child = mSectionsManager.getMediaControlsView();
+                    break;
+                case PEOPLE_HEADER:
+                    child = mSectionsManager.getPeopleHeaderView();
+                    break;
+                case ALERTING_HEADER:
+                    child = mSectionsManager.getAlertingHeaderView();
+                    break;
+                case GENTLE_HEADER:
+                    child = mSectionsManager.getGentleHeaderView();
+                    break;
+                case HEADS_UP:
+                    child = mockNotification(BUCKET_HEADS_UP);
+                    break;
+                case FSN:
+                    child = mockNotification(BUCKET_FOREGROUND_SERVICE);
+                    break;
+                case PERSON:
+                    child = mockNotification(BUCKET_PEOPLE);
+                    break;
+                case ALERTING:
+                    child = mockNotification(BUCKET_ALERTING);
+                    break;
+                case GENTLE:
+                    child = mockNotification(BUCKET_SILENT);
+                    break;
+                case OTHER:
+                    child = mock(View.class);
+                    when(child.getVisibility()).thenReturn(View.VISIBLE);
+                    when(child.getParent()).thenReturn(mNssl);
+                    break;
+                default:
+                    throw new RuntimeException("Unknown ChildType: " + childType);
+            }
+            children.add(child);
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index e04d25b..cc2d1c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -83,7 +83,6 @@
     @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
     @Mock private NotificationShadeDepthController mNotificationShadeDepthController;
     @Mock private SuperStatusBarViewFactory mStatusBarViewFactory;
-    @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
 
     @Before
     public void setUp() {
@@ -122,7 +121,7 @@
                 mNotificationPanelViewController,
                 mStatusBarViewFactory);
         mController.setupExpandedStatusBar();
-        mController.setService(mStatusBar, mNotificationShadeWindowController);
+        mController.setService(mStatusBar);
         mController.setDragDownHelper(mDragDownHelper);
 
     }
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index cbc5e14..1ee017b 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -51,6 +51,11 @@
 cc_library {
     name: "libtetherutilsjni",
     sdk_version: "current",
+    apex_available: [
+        "//apex_available:platform", // Used by InProcessTethering
+        "com.android.tethering",
+    ],
+    min_sdk_version: "current",
     srcs: [
         "jni/android_net_util_TetheringUtils.cpp",
     ],
@@ -110,6 +115,7 @@
     // InProcessTethering is a replacement for Tethering
     overrides: ["Tethering"],
     apex_available: ["com.android.tethering"],
+    min_sdk_version: "current",
 }
 
 // Updatable tethering packaged as an application
@@ -123,4 +129,5 @@
     // The permission configuration *must* be included to ensure security of the device
     required: ["NetworkPermissionConfig"],
     apex_available: ["com.android.tethering"],
+    min_sdk_version: "current",
 }
diff --git a/packages/Tethering/apex/Android.bp b/packages/Tethering/apex/Android.bp
index 20ccd2a..67097a7 100644
--- a/packages/Tethering/apex/Android.bp
+++ b/packages/Tethering/apex/Android.bp
@@ -17,7 +17,7 @@
 apex {
     name: "com.android.tethering",
     updatable: true,
-    min_sdk_version: "R",
+    min_sdk_version: "current",
     java_libs: ["framework-tethering"],
     apps: ["Tethering"],
     manifest: "manifest.json",
diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp
index d029d2b..ae4bb3e 100644
--- a/packages/Tethering/common/TetheringLib/Android.bp
+++ b/packages/Tethering/common/TetheringLib/Android.bp
@@ -13,43 +13,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// AIDL interfaces between the core system and the tethering mainline module.
-aidl_interface {
-    name: "tethering-aidl-interfaces",
-    unstable: true,
-    local_include_dir: "src",
-    include_dirs: ["frameworks/base/core/java"], // For framework parcelables.
-    srcs: [
-        // @JavaOnlyStableParcelable aidl declarations must not be listed here, as this would cause
-        // compilation to fail (b/148001843).
-        "src/android/net/IIntResultListener.aidl",
-        "src/android/net/ITetheringConnector.aidl",
-        "src/android/net/ITetheringEventCallback.aidl",
-        "src/android/net/TetheringCallbackStartedParcel.aidl",
-        "src/android/net/TetheringConfigurationParcel.aidl",
-        "src/android/net/TetheringRequestParcel.aidl",
-        "src/android/net/TetherStatesParcel.aidl",
-    ],
-    backend: {
-        ndk: {
-            enabled: false,
-        },
-        cpp: {
-            enabled: false,
-        },
-    },
-}
-
 java_library {
     name: "framework-tethering",
     sdk_version: "module_current",
     srcs: [
-        "src/android/net/TetheredClient.java",
-        "src/android/net/TetheringManager.java",
-        "src/android/net/TetheringConstants.java",
-    ],
-    static_libs: [
-        "tethering-aidl-interfaces-java",
+        ":framework-tethering-srcs",
     ],
     jarjar_rules: "jarjar-rules.txt",
     installable: true,
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 9dda716..9269c6f 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -57,6 +57,12 @@
         <item>"bt-pan"</item>
     </string-array>
 
+    <!-- Use the BPF offload for tethering when the kernel has support. True by default.
+         If the device doesn't want to support tether BPF offload, this should be false.
+         Note that this setting could be overridden by device config.
+    -->
+    <bool translatable="false" name="config_tether_enable_bpf_offload">true</bool>
+
     <!-- Use the old dnsmasq DHCP server for tethering instead of the framework implementation. -->
     <bool translatable="false" name="config_tether_enable_legacy_dhcp_server">false</bool>
 
diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml
index 4c78a74..4e2bb1e 100644
--- a/packages/Tethering/res/values/overlayable.xml
+++ b/packages/Tethering/res/values/overlayable.xml
@@ -23,6 +23,11 @@
             <item type="array" name="config_tether_wifi_p2p_regexs"/>
             <item type="array" name="config_tether_bluetooth_regexs"/>
             <item type="array" name="config_tether_dhcp_range"/>
+            <!-- Use the BPF offload for tethering when the kernel has support. True by default.
+                 If the device doesn't want to support tether BPF offload, this should be false.
+                 Note that this setting could be overridden by device config.
+            -->
+            <item type="bool" name="config_tether_enable_bpf_offload"/>
             <item type="bool" name="config_tether_enable_legacy_dhcp_server"/>
             <item type="integer" name="config_tether_offload_poll_interval"/>
             <item type="array" name="config_tether_upstream_types"/>
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 83727bc..d993306 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -227,6 +227,7 @@
     private final int mInterfaceType;
     private final LinkProperties mLinkProperties;
     private final boolean mUsingLegacyDhcp;
+    private final boolean mUsingBpfOffload;
 
     private final Dependencies mDeps;
 
@@ -302,9 +303,12 @@
 
     private final IpNeighborMonitor mIpNeighborMonitor;
 
+    // TODO: Add a dependency object to pass the data members or variables from the tethering
+    // object. It helps to reduce the arguments of the constructor.
     public IpServer(
             String ifaceName, Looper looper, int interfaceType, SharedLog log,
-            INetd netd, Callback callback, boolean usingLegacyDhcp, Dependencies deps) {
+            INetd netd, Callback callback, boolean usingLegacyDhcp, boolean usingBpfOffload,
+            Dependencies deps) {
         super(ifaceName, looper);
         mLog = log.forSubComponent(ifaceName);
         mNetd = netd;
@@ -314,6 +318,7 @@
         mInterfaceType = interfaceType;
         mLinkProperties = new LinkProperties();
         mUsingLegacyDhcp = usingLegacyDhcp;
+        mUsingBpfOffload = usingBpfOffload;
         mDeps = deps;
         resetLinkProperties();
         mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
@@ -321,7 +326,12 @@
 
         mIpNeighborMonitor = mDeps.getIpNeighborMonitor(getHandler(), mLog,
                 new MyNeighborEventConsumer());
-        if (!mIpNeighborMonitor.start()) {
+
+        // IP neighbor monitor monitors the neighbor events for adding/removing offload
+        // forwarding rules per client. If BPF offload is not supported, don't start listening
+        // for neighbor events. See updateIpv6ForwardingRules, addIpv6ForwardingRule,
+        // removeIpv6ForwardingRule.
+        if (mUsingBpfOffload && !mIpNeighborMonitor.start()) {
             mLog.e("Failed to create IpNeighborMonitor on " + mIfaceName);
         }
 
@@ -715,12 +725,12 @@
             final String upstreamIface = v6only.getInterfaceName();
 
             params = new RaParams();
-            // We advertise an mtu lower by 16, which is the closest multiple of 8 >= 14,
-            // the ethernet header size.  This makes kernel ebpf tethering offload happy.
-            // This hack should be reverted once we have the kernel fixed up.
+            // When BPF offload is enabled, we advertise an mtu lower by 16, which is the closest
+            // multiple of 8 >= 14, the ethernet header size. This makes kernel ebpf tethering
+            // offload happy. This hack should be reverted once we have the kernel fixed up.
             // Note: this will automatically clamp to at least 1280 (ipv6 minimum mtu)
             // see RouterAdvertisementDaemon.java putMtu()
-            params.mtu = v6only.getMtu() - 16;
+            params.mtu = mUsingBpfOffload ? v6only.getMtu() - 16 : v6only.getMtu();
             params.hasDefaultRoute = v6only.hasIpv6DefaultRoute();
 
             if (params.hasDefaultRoute) params.hopLimit = getHopLimit(upstreamIface);
@@ -844,6 +854,11 @@
     }
 
     private void addIpv6ForwardingRule(Ipv6ForwardingRule rule) {
+        // Theoretically, we don't need this check because IP neighbor monitor doesn't start if BPF
+        // offload is disabled. Add this check just in case.
+        // TODO: Perhaps remove this protection check.
+        if (!mUsingBpfOffload) return;
+
         try {
             mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel());
             mIpv6ForwardingRules.put(rule.address, rule);
@@ -853,6 +868,11 @@
     }
 
     private void removeIpv6ForwardingRule(Ipv6ForwardingRule rule, boolean removeFromMap) {
+        // Theoretically, we don't need this check because IP neighbor monitor doesn't start if BPF
+        // offload is disabled. Add this check just in case.
+        // TODO: Perhaps remove this protection check.
+        if (!mUsingBpfOffload) return;
+
         try {
             mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel());
             if (removeFromMap) {
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 293f8ea..fe92204 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -66,6 +66,7 @@
 
     private final Handler mHandler;
     private final SharedLog mLog;
+    private final Dependencies mDeps;
     private IOffloadControl mOffloadControl;
     private TetheringOffloadCallback mTetheringOffloadCallback;
     private ControlCallback mControlCallback;
@@ -126,8 +127,76 @@
     }
 
     public OffloadHardwareInterface(Handler h, SharedLog log) {
+        this(h, log, new Dependencies(log));
+    }
+
+    OffloadHardwareInterface(Handler h, SharedLog log, Dependencies deps) {
         mHandler = h;
         mLog = log.forSubComponent(TAG);
+        mDeps = deps;
+    }
+
+    /** Capture OffloadHardwareInterface dependencies, for injection. */
+    static class Dependencies {
+        private final SharedLog mLog;
+
+        Dependencies(SharedLog log) {
+            mLog = log;
+        }
+
+        public IOffloadConfig getOffloadConfig() {
+            try {
+                return IOffloadConfig.getService(true /*retry*/);
+            } catch (RemoteException | NoSuchElementException e) {
+                mLog.e("getIOffloadConfig error " + e);
+                return null;
+            }
+        }
+
+        public IOffloadControl getOffloadControl() {
+            try {
+                return IOffloadControl.getService(true /*retry*/);
+            } catch (RemoteException | NoSuchElementException e) {
+                mLog.e("tethering offload control not supported: " + e);
+                return null;
+            }
+        }
+
+        public NativeHandle createConntrackSocket(final int groups) {
+            final FileDescriptor fd;
+            try {
+                fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER);
+            } catch (ErrnoException e) {
+                mLog.e("Unable to create conntrack socket " + e);
+                return null;
+            }
+
+            final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups);
+            try {
+                Os.bind(fd, sockAddr);
+            } catch (ErrnoException | SocketException e) {
+                mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e);
+                try {
+                    SocketUtils.closeSocket(fd);
+                } catch (IOException ie) {
+                    // Nothing we can do here
+                }
+                return null;
+            }
+            try {
+                Os.connect(fd, sockAddr);
+            } catch (ErrnoException | SocketException e) {
+                mLog.e("connect to kernel fail for groups " + groups + " error: " + e);
+                try {
+                    SocketUtils.closeSocket(fd);
+                } catch (IOException ie) {
+                    // Nothing we can do here
+                }
+                return null;
+            }
+
+            return new NativeHandle(fd, true);
+        }
     }
 
     /** Get default value indicating whether offload is supported. */
@@ -141,13 +210,7 @@
      * share them with offload management process.
      */
     public boolean initOffloadConfig() {
-        IOffloadConfig offloadConfig;
-        try {
-            offloadConfig = IOffloadConfig.getService(true /*retry*/);
-        } catch (RemoteException | NoSuchElementException e) {
-            mLog.e("getIOffloadConfig error " + e);
-            return false;
-        }
+        final IOffloadConfig offloadConfig = mDeps.getOffloadConfig();
         if (offloadConfig == null) {
             mLog.e("Could not find IOffloadConfig service");
             return false;
@@ -159,11 +222,11 @@
         //
         // h2    provides a file descriptor bound to the following netlink groups
         //       (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
-        final NativeHandle h1 = createConntrackSocket(
+        final NativeHandle h1 = mDeps.createConntrackSocket(
                 NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
         if (h1 == null) return false;
 
-        final NativeHandle h2 = createConntrackSocket(
+        final NativeHandle h2 = mDeps.createConntrackSocket(
                 NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
         if (h2 == null) {
             closeFdInNativeHandle(h1);
@@ -198,53 +261,12 @@
         }
     }
 
-    private NativeHandle createConntrackSocket(final int groups) {
-        FileDescriptor fd;
-        try {
-            fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER);
-        } catch (ErrnoException e) {
-            mLog.e("Unable to create conntrack socket " + e);
-            return null;
-        }
-
-        final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups);
-        try {
-            Os.bind(fd, sockAddr);
-        } catch (ErrnoException | SocketException e) {
-            mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e);
-            try {
-                SocketUtils.closeSocket(fd);
-            } catch (IOException ie) {
-                // Nothing we can do here
-            }
-            return null;
-        }
-        try {
-            Os.connect(fd, sockAddr);
-        } catch (ErrnoException | SocketException e) {
-            mLog.e("connect to kernel fail for groups " + groups + " error: " + e);
-            try {
-                SocketUtils.closeSocket(fd);
-            } catch (IOException ie) {
-                // Nothing we can do here
-            }
-            return null;
-        }
-
-        return new NativeHandle(fd, true);
-    }
-
     /** Initialize the tethering offload HAL. */
     public boolean initOffloadControl(ControlCallback controlCb) {
         mControlCallback = controlCb;
 
         if (mOffloadControl == null) {
-            try {
-                mOffloadControl = IOffloadControl.getService(true /*retry*/);
-            } catch (RemoteException | NoSuchElementException e) {
-                mLog.e("tethering offload control not supported: " + e);
-                return false;
-            }
+            mOffloadControl = mDeps.getOffloadControl();
             if (mOffloadControl == null) {
                 mLog.e("tethering IOffloadControl.getService() returned null");
                 return false;
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
index ae6119f2..04ad43f 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -109,8 +109,10 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.telephony.PhoneStateListener;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -228,6 +230,7 @@
     private final ConnectedClientsTracker mConnectedClientsTracker;
     private final TetheringThreadExecutor mExecutor;
     private final TetheringNotificationUpdater mNotificationUpdater;
+    private final UserManager mUserManager;
     private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
     // All the usage of mTetheringEventCallback should run in the same thread.
     private ITetheringEventCallback mTetheringEventCallback = null;
@@ -305,23 +308,24 @@
 
         mStateReceiver = new StateReceiver();
 
-        final UserManager userManager = (UserManager) mContext.getSystemService(
-                Context.USER_SERVICE);
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mTetheringRestriction = new UserRestrictionActionListener(
-                userManager, this, mNotificationUpdater);
+                mUserManager, this, mNotificationUpdater);
         mExecutor = new TetheringThreadExecutor(mHandler);
         mActiveDataSubIdListener = new ActiveDataSubIdListener(mExecutor);
         mNetdCallback = new NetdCallback();
 
         // Load tethering configuration.
         updateConfiguration();
+
+        startStateMachineUpdaters();
     }
 
     /**
      * Start to register callbacks.
      * Call this function when tethering is ready to handle callback events.
      */
-    public void startStateMachineUpdaters() {
+    private void startStateMachineUpdaters() {
         try {
             mNetd.registerUnsolicitedEventListener(mNetdCallback);
         } catch (RemoteException e) {
@@ -779,7 +783,7 @@
 
     // TODO: Figure out how to update for local hotspot mode interfaces.
     private void sendTetherStateChangedBroadcast() {
-        if (!mDeps.isTetheringSupported()) return;
+        if (!isTetheringSupported()) return;
 
         final ArrayList<String> availableList = new ArrayList<>();
         final ArrayList<String> tetherList = new ArrayList<>();
@@ -1020,14 +1024,14 @@
 
     @VisibleForTesting
     protected static class UserRestrictionActionListener {
-        private final UserManager mUserManager;
+        private final UserManager mUserMgr;
         private final Tethering mWrapper;
         private final TetheringNotificationUpdater mNotificationUpdater;
         public boolean mDisallowTethering;
 
         public UserRestrictionActionListener(@NonNull UserManager um, @NonNull Tethering wrapper,
                 @NonNull TetheringNotificationUpdater updater) {
-            mUserManager = um;
+            mUserMgr = um;
             mWrapper = wrapper;
             mNotificationUpdater = updater;
             mDisallowTethering = false;
@@ -1037,7 +1041,7 @@
             // getUserRestrictions gets restriction for this process' user, which is the primary
             // user. This is fine because DISALLOW_CONFIG_TETHERING can only be set on the primary
             // user. See UserManager.DISALLOW_CONFIG_TETHERING.
-            final Bundle restrictions = mUserManager.getUserRestrictions();
+            final Bundle restrictions = mUserMgr.getUserRestrictions();
             final boolean newlyDisallowed =
                     restrictions.getBoolean(UserManager.DISALLOW_CONFIG_TETHERING);
             final boolean prevDisallowed = mDisallowTethering;
@@ -1988,7 +1992,7 @@
         mHandler.post(() -> {
             mTetheringEventCallbacks.register(callback, new CallbackCookie(hasListPermission));
             final TetheringCallbackStartedParcel parcel = new TetheringCallbackStartedParcel();
-            parcel.tetheringSupported = mDeps.isTetheringSupported();
+            parcel.tetheringSupported = isTetheringSupported();
             parcel.upstreamNetwork = mTetherUpstream;
             parcel.config = mConfig.toStableParcelable();
             parcel.states =
@@ -2111,6 +2115,20 @@
         }
     }
 
+    // if ro.tether.denied = true we default to no tethering
+    // gservices could set the secure setting to 1 though to enable it on a build where it
+    // had previously been turned off.
+    boolean isTetheringSupported() {
+        final int defaultVal =
+                SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1;
+        final boolean tetherSupported = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.TETHER_SUPPORTED, defaultVal) != 0;
+        final boolean tetherEnabledInSettings = tetherSupported
+                && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
+
+        return tetherEnabledInSettings && hasTetherableConfiguration();
+    }
+
     void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) {
         // Binder.java closes the resource for us.
         @SuppressWarnings("resource")
@@ -2296,7 +2314,7 @@
         final TetherState tetherState = new TetherState(
                 new IpServer(iface, mLooper, interfaceType, mLog, mNetd,
                              makeControlCallback(), mConfig.enableLegacyDhcpServer,
-                             mDeps.getIpServerDependencies()));
+                             mConfig.enableBpfOffload, mDeps.getIpServerDependencies()));
         mTetherStates.put(iface, tetherState);
         tetherState.ipServer.start();
     }
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 9d4e747..91a6e29 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -73,6 +73,12 @@
     private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
 
     /**
+     * Override enabling BPF offload configuration for tethering.
+     */
+    public static final String OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD =
+            "override_tether_enable_bpf_offload";
+
+    /**
      * Use the old dnsmasq DHCP server for tethering instead of the framework implementation.
      */
     public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER =
@@ -95,6 +101,8 @@
     public final String[] legacyDhcpRanges;
     public final String[] defaultIPv4DNS;
     public final boolean enableLegacyDhcpServer;
+    // TODO: Add to TetheringConfigurationParcel if required.
+    public final boolean enableBpfOffload;
 
     public final String[] provisioningApp;
     public final String provisioningAppNoUi;
@@ -124,11 +132,12 @@
         isDunRequired = checkDunRequired(ctx);
 
         chooseUpstreamAutomatically = getResourceBoolean(
-                res, R.bool.config_tether_upstream_automatic);
+                res, R.bool.config_tether_upstream_automatic, false /** default value */);
         preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
 
         legacyDhcpRanges = getLegacyDhcpRanges(res);
         defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
+        enableBpfOffload = getEnableBpfOffload(res);
         enableLegacyDhcpServer = getEnableLegacyDhcpServer(res);
 
         provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app);
@@ -208,6 +217,9 @@
         pw.print("provisioningAppNoUi: ");
         pw.println(provisioningAppNoUi);
 
+        pw.print("enableBpfOffload: ");
+        pw.println(enableBpfOffload);
+
         pw.print("enableLegacyDhcpServer: ");
         pw.println(enableLegacyDhcpServer);
     }
@@ -228,6 +240,7 @@
                 toIntArray(preferredUpstreamIfaceTypes)));
         sj.add(String.format("provisioningApp:%s", makeString(provisioningApp)));
         sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi));
+        sj.add(String.format("enableBpfOffload:%s", enableBpfOffload));
         sj.add(String.format("enableLegacyDhcpServer:%s", enableLegacyDhcpServer));
         return String.format("TetheringConfiguration{%s}", sj.toString());
     }
@@ -332,11 +345,11 @@
         }
     }
 
-    private static boolean getResourceBoolean(Resources res, int resId) {
+    private static boolean getResourceBoolean(Resources res, int resId, boolean defaultValue) {
         try {
             return res.getBoolean(resId);
         } catch (Resources.NotFoundException e404) {
-            return false;
+            return defaultValue;
         }
     }
 
@@ -357,8 +370,29 @@
         }
     }
 
+    private boolean getEnableBpfOffload(final Resources res) {
+        // Get BPF offload config
+        // Priority 1: Device config
+        // Priority 2: Resource config
+        // Priority 3: Default value
+        final boolean resourceValue = getResourceBoolean(
+                res, R.bool.config_tether_enable_bpf_offload, true /** default value */);
+
+        // Due to the limitation of static mock for testing, using #getProperty directly instead
+        // of getDeviceConfigBoolean. getDeviceConfigBoolean is not invoked because it uses
+        // #getBoolean to get the boolean device config. The test can't know that the returned
+        // boolean value comes from device config or default value (because of null property
+        // string). Because the test would like to verify null property boolean string case,
+        // use DeviceConfig.getProperty here. See also the test case testBpfOffload{*} in
+        // TetheringConfigurationTest.java.
+        final String value = DeviceConfig.getProperty(
+                NAMESPACE_CONNECTIVITY, OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD);
+        return (value != null) ? Boolean.parseBoolean(value) : resourceValue;
+    }
+
     private boolean getEnableLegacyDhcpServer(final Resources res) {
-        return getResourceBoolean(res, R.bool.config_tether_enable_legacy_dhcp_server)
+        return getResourceBoolean(
+                res, R.bool.config_tether_enable_legacy_dhcp_server, false /** default value */)
                 || getDeviceConfigBoolean(TETHER_ENABLE_LEGACY_DHCP_SERVER);
     }
 
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index d07c555f..bf7fb04 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -40,15 +40,12 @@
 import android.net.dhcp.DhcpServerCallbacks;
 import android.net.dhcp.DhcpServingParamsParcel;
 import android.net.ip.IpServer;
-import android.net.util.SharedLog;
 import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
-import android.os.SystemProperties;
-import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
 
@@ -68,21 +65,14 @@
 public class TetheringService extends Service {
     private static final String TAG = TetheringService.class.getSimpleName();
 
-    private final SharedLog mLog = new SharedLog(TAG);
     private TetheringConnector mConnector;
-    private Context mContext;
-    private TetheringDependencies mDeps;
-    private Tethering mTethering;
-    private UserManager mUserManager;
 
     @Override
     public void onCreate() {
-        mLog.mark("onCreate");
-        mDeps = getTetheringDependencies();
-        mContext = mDeps.getContext();
-        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mTethering = makeTethering(mDeps);
-        mTethering.startStateMachineUpdaters();
+        final TetheringDependencies deps = makeTetheringDependencies();
+        // The Tethering object needs a fully functional context to start, so this can't be done
+        // in the constructor.
+        mConnector = new TetheringConnector(makeTethering(deps), TetheringService.this);
     }
 
     /**
@@ -94,21 +84,10 @@
         return new Tethering(deps);
     }
 
-    /**
-     * Create a binder connector for the system server to communicate with the tethering.
-     */
-    private synchronized IBinder makeConnector() {
-        if (mConnector == null) {
-            mConnector = new TetheringConnector(mTethering, TetheringService.this);
-        }
-        return mConnector;
-    }
-
     @NonNull
     @Override
     public IBinder onBind(Intent intent) {
-        mLog.mark("onBind");
-        return makeConnector();
+        return mConnector;
     }
 
     private static class TetheringConnector extends ITetheringConnector.Stub {
@@ -237,7 +216,7 @@
                     listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
                     return true;
                 }
-                if (!mService.isTetheringSupported()) {
+                if (!mTethering.isTetheringSupported()) {
                     listener.onResult(TETHER_ERROR_UNSUPPORTED);
                     return true;
                 }
@@ -253,7 +232,7 @@
                 receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null);
                 return true;
             }
-            if (!mService.isTetheringSupported()) {
+            if (!mTethering.isTetheringSupported()) {
                 receiver.send(TETHER_ERROR_UNSUPPORTED, null);
                 return true;
             }
@@ -287,105 +266,83 @@
         }
     }
 
-    // if ro.tether.denied = true we default to no tethering
-    // gservices could set the secure setting to 1 though to enable it on a build where it
-    // had previously been turned off.
-    private boolean isTetheringSupported() {
-        final int defaultVal =
-                SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1;
-        final boolean tetherSupported = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.TETHER_SUPPORTED, defaultVal) != 0;
-        final boolean tetherEnabledInSettings = tetherSupported
-                && !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING);
-
-        return tetherEnabledInSettings && mTethering.hasTetherableConfiguration();
-    }
-
     /**
      * An injection method for testing.
      */
     @VisibleForTesting
-    public TetheringDependencies getTetheringDependencies() {
-        if (mDeps == null) {
-            mDeps = new TetheringDependencies() {
-                @Override
-                public NetworkRequest getDefaultNetworkRequest() {
-                    // TODO: b/147280869, add a proper system API to replace this.
-                    final NetworkRequest trackDefaultRequest = new NetworkRequest.Builder()
-                            .clearCapabilities()
-                            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
-                            .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
-                            .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
-                            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                            .build();
-                    return trackDefaultRequest;
-                }
+    public TetheringDependencies makeTetheringDependencies() {
+        return new TetheringDependencies() {
+            @Override
+            public NetworkRequest getDefaultNetworkRequest() {
+                // TODO: b/147280869, add a proper system API to replace this.
+                final NetworkRequest trackDefaultRequest = new NetworkRequest.Builder()
+                        .clearCapabilities()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        .build();
+                return trackDefaultRequest;
+            }
 
-                @Override
-                public Looper getTetheringLooper() {
-                    final HandlerThread tetherThread = new HandlerThread("android.tethering");
-                    tetherThread.start();
-                    return tetherThread.getLooper();
-                }
+            @Override
+            public Looper getTetheringLooper() {
+                final HandlerThread tetherThread = new HandlerThread("android.tethering");
+                tetherThread.start();
+                return tetherThread.getLooper();
+            }
 
-                @Override
-                public boolean isTetheringSupported() {
-                    return TetheringService.this.isTetheringSupported();
-                }
+            @Override
+            public Context getContext() {
+                return TetheringService.this;
+            }
 
-                @Override
-                public Context getContext() {
-                    return TetheringService.this;
-                }
+            @Override
+            public IpServer.Dependencies getIpServerDependencies() {
+                return new IpServer.Dependencies() {
+                    @Override
+                    public void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
+                            DhcpServerCallbacks cb) {
+                        try {
+                            final INetworkStackConnector service = getNetworkStackConnector();
+                            if (service == null) return;
 
-                @Override
-                public IpServer.Dependencies getIpServerDependencies() {
-                    return new IpServer.Dependencies() {
-                        @Override
-                        public void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
-                                DhcpServerCallbacks cb) {
+                            service.makeDhcpServer(ifName, params, cb);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Fail to make dhcp server");
                             try {
-                                final INetworkStackConnector service = getNetworkStackConnector();
-                                if (service == null) return;
-
-                                service.makeDhcpServer(ifName, params, cb);
-                            } catch (RemoteException e) {
-                                Log.e(TAG, "Fail to make dhcp server");
-                                try {
-                                    cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null);
-                                } catch (RemoteException re) { }
-                            }
+                                cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null);
+                            } catch (RemoteException re) { }
                         }
-                    };
-                }
-
-                // TODO: replace this by NetworkStackClient#getRemoteConnector after refactoring
-                // networkStackClient.
-                static final int NETWORKSTACK_TIMEOUT_MS = 60_000;
-                private INetworkStackConnector getNetworkStackConnector() {
-                    IBinder connector;
-                    try {
-                        final long before = System.currentTimeMillis();
-                        while ((connector = NetworkStack.getService()) == null) {
-                            if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) {
-                                Log.wtf(TAG, "Timeout, fail to get INetworkStackConnector");
-                                return null;
-                            }
-                            Thread.sleep(200);
-                        }
-                    } catch (InterruptedException e) {
-                        Log.wtf(TAG, "Interrupted, fail to get INetworkStackConnector");
-                        return null;
                     }
-                    return INetworkStackConnector.Stub.asInterface(connector);
-                }
+                };
+            }
 
-                @Override
-                public BluetoothAdapter getBluetoothAdapter() {
-                    return BluetoothAdapter.getDefaultAdapter();
+            // TODO: replace this by NetworkStackClient#getRemoteConnector after refactoring
+            // networkStackClient.
+            static final int NETWORKSTACK_TIMEOUT_MS = 60_000;
+            private INetworkStackConnector getNetworkStackConnector() {
+                IBinder connector;
+                try {
+                    final long before = System.currentTimeMillis();
+                    while ((connector = NetworkStack.getService()) == null) {
+                        if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) {
+                            Log.wtf(TAG, "Timeout, fail to get INetworkStackConnector");
+                            return null;
+                        }
+                        Thread.sleep(200);
+                    }
+                } catch (InterruptedException e) {
+                    Log.wtf(TAG, "Interrupted, fail to get INetworkStackConnector");
+                    return null;
                 }
-            };
-        }
-        return mDeps;
+                return INetworkStackConnector.Stub.asInterface(connector);
+            }
+
+            @Override
+            public BluetoothAdapter getBluetoothAdapter() {
+                return BluetoothAdapter.getDefaultAdapter();
+            }
+        };
     }
 }
diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index f9be7b9..b9622da 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -106,6 +106,7 @@
     private static final String BLUETOOTH_IFACE_ADDR = "192.168.42.1";
     private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
     private static final int DHCP_LEASE_TIME_SECS = 3600;
+    private static final boolean DEFAULT_USING_BPF_OFFLOAD = true;
 
     private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
             IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */);
@@ -130,10 +131,11 @@
     private NeighborEventConsumer mNeighborEventConsumer;
 
     private void initStateMachine(int interfaceType) throws Exception {
-        initStateMachine(interfaceType, false /* usingLegacyDhcp */);
+        initStateMachine(interfaceType, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD);
     }
 
-    private void initStateMachine(int interfaceType, boolean usingLegacyDhcp) throws Exception {
+    private void initStateMachine(int interfaceType, boolean usingLegacyDhcp,
+            boolean usingBpfOffload) throws Exception {
         doAnswer(inv -> {
             final IDhcpServerCallbacks cb = inv.getArgument(2);
             new Thread(() -> {
@@ -165,7 +167,7 @@
 
         mIpServer = new IpServer(
                 IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd,
-                mCallback, usingLegacyDhcp, mDependencies);
+                mCallback, usingLegacyDhcp, usingBpfOffload, mDependencies);
         mIpServer.start();
         mNeighborEventConsumer = neighborCaptor.getValue();
 
@@ -179,12 +181,13 @@
 
     private void initTetheredStateMachine(int interfaceType, String upstreamIface)
             throws Exception {
-        initTetheredStateMachine(interfaceType, upstreamIface, false);
+        initTetheredStateMachine(interfaceType, upstreamIface, false,
+                DEFAULT_USING_BPF_OFFLOAD);
     }
 
     private void initTetheredStateMachine(int interfaceType, String upstreamIface,
-            boolean usingLegacyDhcp) throws Exception {
-        initStateMachine(interfaceType, usingLegacyDhcp);
+            boolean usingLegacyDhcp, boolean usingBpfOffload) throws Exception {
+        initStateMachine(interfaceType, usingLegacyDhcp, usingBpfOffload);
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
         if (upstreamIface != null) {
             LinkProperties lp = new LinkProperties();
@@ -204,7 +207,8 @@
         when(mDependencies.getIpNeighborMonitor(any(), any(), any()))
                 .thenReturn(mIpNeighborMonitor);
         mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
-                mNetd, mCallback, false /* usingLegacyDhcp */, mDependencies);
+                mNetd, mCallback, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD,
+                mDependencies);
         mIpServer.start();
         mLooper.dispatchAll();
         verify(mCallback).updateInterfaceState(
@@ -494,7 +498,8 @@
 
     @Test
     public void doesNotStartDhcpServerIfDisabled() throws Exception {
-        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */);
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */,
+                DEFAULT_USING_BPF_OFFLOAD);
         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
 
         verify(mDependencies, never()).makeDhcpServer(any(), any(), any());
@@ -577,7 +582,8 @@
 
     @Test
     public void addRemoveipv6ForwardingRules() throws Exception {
-        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */);
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
+                DEFAULT_USING_BPF_OFFLOAD);
 
         final int myIfindex = TEST_IFACE_PARAMS.index;
         final int notMyIfindex = myIfindex - 1;
@@ -678,6 +684,53 @@
         reset(mNetd);
     }
 
+    @Test
+    public void enableDisableUsingBpfOffload() throws Exception {
+        final int myIfindex = TEST_IFACE_PARAMS.index;
+        final InetAddress neigh = InetAddresses.parseNumericAddress("2001:db8::1");
+        final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a");
+        final MacAddress macNull = MacAddress.fromString("00:00:00:00:00:00");
+
+        reset(mNetd);
+
+        // Expect that rules can be only added/removed when the BPF offload config is enabled.
+        // Note that the usingBpfOffload false case is not a realistic test case. Because IP
+        // neighbor monitor doesn't start if BPF offload is disabled, there should have no
+        // neighbor event listening. This is used for testing the protection check just in case.
+        // TODO: Perhaps remove this test once we don't need this check anymore.
+        for (boolean usingBpfOffload : new boolean[]{true, false}) {
+            initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
+                    usingBpfOffload);
+
+            // A neighbor is added.
+            recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
+            if (usingBpfOffload) {
+                verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neigh, macA));
+            } else {
+                verify(mNetd, never()).tetherOffloadRuleAdd(any());
+            }
+            reset(mNetd);
+
+            // A neighbor is deleted.
+            recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
+            if (usingBpfOffload) {
+                verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neigh, macNull));
+            } else {
+                verify(mNetd, never()).tetherOffloadRuleRemove(any());
+            }
+            reset(mNetd);
+        }
+    }
+
+    @Test
+    public void doesNotStartIpNeighborMonitorIfBpfOffloadDisabled() throws Exception {
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
+                false /* usingBpfOffload */);
+
+        // IP neighbor monitor doesn't start if BPF offload is disabled.
+        verify(mIpNeighborMonitor, never()).start();
+    }
+
     private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
         verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any());
         verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).startWithCallbacks(
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
new file mode 100644
index 0000000..f8ff1cb
--- /dev/null
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadHardwareInterfaceTest.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.tethering;
+
+import static android.net.util.TetheringUtils.uint16;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
+import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
+import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
+import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
+import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
+import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.NativeHandle;
+import android.os.test.TestLooper;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+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 java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class OffloadHardwareInterfaceTest {
+    private static final String RMNET0 = "test_rmnet_data0";
+
+    private final TestLooper mTestLooper = new TestLooper();
+
+    private OffloadHardwareInterface mOffloadHw;
+    private ITetheringOffloadCallback mTetheringOffloadCallback;
+    private OffloadHardwareInterface.ControlCallback mControlCallback;
+
+    @Mock private IOffloadConfig mIOffloadConfig;
+    @Mock private IOffloadControl mIOffloadControl;
+    @Mock private NativeHandle mNativeHandle;
+
+    class MyDependencies extends OffloadHardwareInterface.Dependencies {
+        MyDependencies(SharedLog log) {
+            super(log);
+        }
+
+        @Override
+        public IOffloadConfig getOffloadConfig() {
+            return mIOffloadConfig;
+        }
+
+        @Override
+        public IOffloadControl getOffloadControl() {
+            return mIOffloadControl;
+        }
+
+        @Override
+        public NativeHandle createConntrackSocket(final int groups) {
+            return mNativeHandle;
+        }
+    }
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        final SharedLog log = new SharedLog("test");
+        mOffloadHw = new OffloadHardwareInterface(new Handler(mTestLooper.getLooper()), log,
+                new MyDependencies(log));
+        mControlCallback = spy(new OffloadHardwareInterface.ControlCallback());
+    }
+
+    private void startOffloadHardwareInterface() throws Exception {
+        mOffloadHw.initOffloadConfig();
+        mOffloadHw.initOffloadControl(mControlCallback);
+        final ArgumentCaptor<ITetheringOffloadCallback> mOffloadCallbackCaptor =
+                ArgumentCaptor.forClass(ITetheringOffloadCallback.class);
+        verify(mIOffloadControl).initOffload(mOffloadCallbackCaptor.capture(), any());
+        mTetheringOffloadCallback = mOffloadCallbackCaptor.getValue();
+    }
+
+    @Test
+    public void testGetForwardedStats() throws Exception {
+        startOffloadHardwareInterface();
+        final OffloadHardwareInterface.ForwardedStats stats = mOffloadHw.getForwardedStats(RMNET0);
+        verify(mIOffloadControl).getForwardedStats(eq(RMNET0), any());
+        assertNotNull(stats);
+    }
+
+    @Test
+    public void testSetLocalPrefixes() throws Exception {
+        startOffloadHardwareInterface();
+        final ArrayList<String> localPrefixes = new ArrayList<>();
+        localPrefixes.add("127.0.0.0/8");
+        localPrefixes.add("fe80::/64");
+        mOffloadHw.setLocalPrefixes(localPrefixes);
+        verify(mIOffloadControl).setLocalPrefixes(eq(localPrefixes), any());
+    }
+
+    @Test
+    public void testSetDataLimit() throws Exception {
+        startOffloadHardwareInterface();
+        final long limit = 12345;
+        mOffloadHw.setDataLimit(RMNET0, limit);
+        verify(mIOffloadControl).setDataLimit(eq(RMNET0), eq(limit), any());
+    }
+
+    @Test
+    public void testSetUpstreamParameters() throws Exception {
+        startOffloadHardwareInterface();
+        final String v4addr = "192.168.10.1";
+        final String v4gateway = "192.168.10.255";
+        final ArrayList<String> v6gws = new ArrayList<>(0);
+        v6gws.add("2001:db8::1");
+        mOffloadHw.setUpstreamParameters(RMNET0, v4addr, v4gateway, v6gws);
+        verify(mIOffloadControl).setUpstreamParameters(eq(RMNET0), eq(v4addr), eq(v4gateway),
+                eq(v6gws), any());
+
+        final ArgumentCaptor<ArrayList<String>> mArrayListCaptor =
+                ArgumentCaptor.forClass(ArrayList.class);
+        mOffloadHw.setUpstreamParameters(null, null, null, null);
+        verify(mIOffloadControl).setUpstreamParameters(eq(""), eq(""), eq(""),
+                mArrayListCaptor.capture(), any());
+        assertEquals(mArrayListCaptor.getValue().size(), 0);
+    }
+
+    @Test
+    public void testUpdateDownstreamPrefix() throws Exception {
+        startOffloadHardwareInterface();
+        final String ifName = "wlan1";
+        final String prefix = "192.168.43.0/24";
+        mOffloadHw.addDownstreamPrefix(ifName, prefix);
+        verify(mIOffloadControl).addDownstream(eq(ifName), eq(prefix), any());
+
+        mOffloadHw.removeDownstreamPrefix(ifName, prefix);
+        verify(mIOffloadControl).removeDownstream(eq(ifName), eq(prefix), any());
+    }
+
+    @Test
+    public void testTetheringOffloadCallback() throws Exception {
+        startOffloadHardwareInterface();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STARTED);
+        mTestLooper.dispatchAll();
+        verify(mControlCallback).onStarted();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR);
+        mTestLooper.dispatchAll();
+        verify(mControlCallback).onStoppedError();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED);
+        mTestLooper.dispatchAll();
+        verify(mControlCallback).onStoppedUnsupported();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE);
+        mTestLooper.dispatchAll();
+        verify(mControlCallback).onSupportAvailable();
+
+        mTetheringOffloadCallback.onEvent(OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED);
+        mTestLooper.dispatchAll();
+        verify(mControlCallback).onStoppedLimitReached();
+
+        final NatTimeoutUpdate tcpParams = buildNatTimeoutUpdate(NetworkProtocol.TCP);
+        mTetheringOffloadCallback.updateTimeout(tcpParams);
+        mTestLooper.dispatchAll();
+        verify(mControlCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_TCP),
+                eq(tcpParams.src.addr),
+                eq(uint16(tcpParams.src.port)),
+                eq(tcpParams.dst.addr),
+                eq(uint16(tcpParams.dst.port)));
+
+        final NatTimeoutUpdate udpParams = buildNatTimeoutUpdate(NetworkProtocol.UDP);
+        mTetheringOffloadCallback.updateTimeout(udpParams);
+        mTestLooper.dispatchAll();
+        verify(mControlCallback).onNatTimeoutUpdate(eq(OsConstants.IPPROTO_UDP),
+                eq(udpParams.src.addr),
+                eq(uint16(udpParams.src.port)),
+                eq(udpParams.dst.addr),
+                eq(uint16(udpParams.dst.port)));
+    }
+
+    private NatTimeoutUpdate buildNatTimeoutUpdate(final int proto) {
+        final NatTimeoutUpdate params = new NatTimeoutUpdate();
+        params.proto = proto;
+        params.src.addr = "192.168.43.200";
+        params.src.port = 100;
+        params.dst.addr = "172.50.46.169";
+        params.dst.port = 150;
+        return params;
+    }
+}
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
index e8ba5b8..fbfa871 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringConfigurationTest.java
@@ -127,6 +127,8 @@
                 .thenReturn(new String[0]);
         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
                 false);
+        initializeBpfOffloadConfiguration(true, null /* unset */);
+
         mHasTelephonyManager = true;
         mMockContext = new MockContext(mContext);
         mEnableLegacyDhcpServer = false;
@@ -278,6 +280,50 @@
         assertFalse(upstreamIterator.hasNext());
     }
 
+    private void initializeBpfOffloadConfiguration(
+            final boolean fromRes, final String fromDevConfig) {
+        when(mResources.getBoolean(R.bool.config_tether_enable_bpf_offload)).thenReturn(fromRes);
+        doReturn(fromDevConfig).when(
+                () -> DeviceConfig.getProperty(eq(NAMESPACE_CONNECTIVITY),
+                eq(TetheringConfiguration.OVERRIDE_TETHER_ENABLE_BPF_OFFLOAD)));
+    }
+
+    @Test
+    public void testBpfOffloadEnabledByResource() {
+        initializeBpfOffloadConfiguration(true, null /* unset */);
+        final TetheringConfiguration enableByRes =
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        assertTrue(enableByRes.enableBpfOffload);
+    }
+
+    @Test
+    public void testBpfOffloadEnabledByDeviceConfigOverride() {
+        for (boolean res : new boolean[]{true, false}) {
+            initializeBpfOffloadConfiguration(res, "true");
+            final TetheringConfiguration enableByDevConOverride =
+                    new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+            assertTrue(enableByDevConOverride.enableBpfOffload);
+        }
+    }
+
+    @Test
+    public void testBpfOffloadDisabledByResource() {
+        initializeBpfOffloadConfiguration(false, null /* unset */);
+        final TetheringConfiguration disableByRes =
+                new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        assertFalse(disableByRes.enableBpfOffload);
+    }
+
+    @Test
+    public void testBpfOffloadDisabledByDeviceConfigOverride() {
+        for (boolean res : new boolean[]{true, false}) {
+            initializeBpfOffloadConfiguration(res, "false");
+            final TetheringConfiguration disableByDevConOverride =
+                    new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+            assertFalse(disableByDevConOverride.enableBpfOffload);
+        }
+    }
+
     @Test
     public void testNewDhcpServerDisabled() {
         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index 51bad9a..4a667b1 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -82,8 +82,7 @@
         mTetheringConnector = mockConnector.getTetheringConnector();
         final MockTetheringService service = mockConnector.getService();
         mTethering = service.getTethering();
-        verify(mTethering).startStateMachineUpdaters();
-        when(mTethering.hasTetherableConfiguration()).thenReturn(true);
+        when(mTethering.isTetheringSupported()).thenReturn(true);
     }
 
     @After
@@ -96,7 +95,7 @@
         when(mTethering.tether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR);
         final TestTetheringResult result = new TestTetheringResult();
         mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, result);
-        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).isTetheringSupported();
         verify(mTethering).tether(TEST_IFACE_NAME);
         verifyNoMoreInteractions(mTethering);
         result.assertResult(TETHER_ERROR_NO_ERROR);
@@ -107,7 +106,7 @@
         when(mTethering.untether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR);
         final TestTetheringResult result = new TestTetheringResult();
         mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, result);
-        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).isTetheringSupported();
         verify(mTethering).untether(TEST_IFACE_NAME);
         verifyNoMoreInteractions(mTethering);
         result.assertResult(TETHER_ERROR_NO_ERROR);
@@ -118,7 +117,7 @@
         when(mTethering.setUsbTethering(true /* enable */)).thenReturn(TETHER_ERROR_NO_ERROR);
         final TestTetheringResult result = new TestTetheringResult();
         mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, result);
-        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).isTetheringSupported();
         verify(mTethering).setUsbTethering(true /* enable */);
         verifyNoMoreInteractions(mTethering);
         result.assertResult(TETHER_ERROR_NO_ERROR);
@@ -130,7 +129,7 @@
         final TetheringRequestParcel request = new TetheringRequestParcel();
         request.tetheringType = TETHERING_WIFI;
         mTetheringConnector.startTethering(request, TEST_CALLER_PKG, result);
-        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).isTetheringSupported();
         verify(mTethering).startTethering(eq(request), eq(result));
         verifyNoMoreInteractions(mTethering);
     }
@@ -139,7 +138,7 @@
     public void testStopTethering() throws Exception {
         final TestTetheringResult result = new TestTetheringResult();
         mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, result);
-        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).isTetheringSupported();
         verify(mTethering).stopTethering(TETHERING_WIFI);
         verifyNoMoreInteractions(mTethering);
         result.assertResult(TETHER_ERROR_NO_ERROR);
@@ -150,7 +149,7 @@
         final ResultReceiver result = new ResultReceiver(null);
         mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
                 true /* showEntitlementUi */, TEST_CALLER_PKG);
-        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).isTetheringSupported();
         verify(mTethering).requestLatestTetheringEntitlementResult(eq(TETHERING_WIFI),
                 eq(result), eq(true) /* showEntitlementUi */);
         verifyNoMoreInteractions(mTethering);
@@ -177,7 +176,7 @@
     public void testStopAllTethering() throws Exception {
         final TestTetheringResult result = new TestTetheringResult();
         mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, result);
-        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).isTetheringSupported();
         verify(mTethering).untetherAll();
         verifyNoMoreInteractions(mTethering);
         result.assertResult(TETHER_ERROR_NO_ERROR);
@@ -187,7 +186,7 @@
     public void testIsTetheringSupported() throws Exception {
         final TestTetheringResult result = new TestTetheringResult();
         mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, result);
-        verify(mTethering).hasTetherableConfiguration();
+        verify(mTethering).isTetheringSupported();
         verifyNoMoreInteractions(mTethering);
         result.assertResult(TETHER_ERROR_NO_ERROR);
     }
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 2bd8ae0..00174e6 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -485,18 +485,6 @@
         MockitoAnnotations.initMocks(this);
         when(mResources.getStringArray(R.array.config_tether_dhcp_range))
                 .thenReturn(new String[0]);
-        when(mResources.getStringArray(R.array.config_tether_usb_regexs))
-                .thenReturn(new String[] { "test_rndis\\d" });
-        when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
-                .thenReturn(new String[]{ "test_wlan\\d" });
-        when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
-                .thenReturn(new String[]{ "test_p2p-p2p\\d-.*" });
-        when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
-                .thenReturn(new String[0]);
-        when(mResources.getStringArray(R.array.config_tether_ncm_regexs))
-                .thenReturn(new String[] { "test_ncm\\d" });
-        when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
-        when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(false);
         when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
                 false);
         when(mNetd.interfaceGetList())
@@ -515,6 +503,7 @@
         mServiceContext = new TestContext(mContext);
         mContentResolver = new MockContentResolver(mServiceContext);
         mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        setTetheringSupported(true /* supported */);
         mIntents = new Vector<>();
         mBroadcastReceiver = new BroadcastReceiver() {
             @Override
@@ -525,7 +514,6 @@
         mServiceContext.registerReceiver(mBroadcastReceiver,
                 new IntentFilter(ACTION_TETHER_STATE_CHANGED));
         mTethering = makeTethering();
-        mTethering.startStateMachineUpdaters();
         verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any());
         verify(mNetd).registerUnsolicitedEventListener(any());
         final ArgumentCaptor<PhoneStateListener> phoneListenerCaptor =
@@ -536,6 +524,31 @@
         mPhoneStateListener = phoneListenerCaptor.getValue();
     }
 
+    private void setTetheringSupported(final boolean supported) {
+        Settings.Global.putInt(mContentResolver, Settings.Global.TETHER_SUPPORTED,
+                supported ? 1 : 0);
+        when(mUserManager.hasUserRestriction(
+                UserManager.DISALLOW_CONFIG_TETHERING)).thenReturn(!supported);
+        // Setup tetherable configuration.
+        when(mResources.getStringArray(R.array.config_tether_usb_regexs))
+                .thenReturn(new String[] { "test_rndis\\d" });
+        when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
+                .thenReturn(new String[]{ "test_wlan\\d" });
+        when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
+                .thenReturn(new String[]{ "test_p2p-p2p\\d-.*" });
+        when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
+                .thenReturn(new String[0]);
+        when(mResources.getStringArray(R.array.config_tether_ncm_regexs))
+                .thenReturn(new String[] { "test_ncm\\d" });
+        when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
+        when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true);
+    }
+
+    private void initTetheringUpstream(UpstreamNetworkState upstreamState) {
+        when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
+        when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())).thenReturn(upstreamState);
+    }
+
     private Tethering makeTethering() {
         mTetheringDependencies.reset();
         return new Tethering(mTetheringDependencies);
@@ -672,9 +685,7 @@
     }
 
     private void prepareUsbTethering(UpstreamNetworkState upstreamState) {
-        when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
-        when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any()))
-                .thenReturn(upstreamState);
+        initTetheringUpstream(upstreamState);
 
         // Emulate pressing the USB tethering button in Settings UI.
         mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB), null);
@@ -700,7 +711,7 @@
         verify(mNetd, times(1)).interfaceGetList();
 
         // UpstreamNetworkMonitor should receive selected upstream
-        verify(mUpstreamNetworkMonitor, times(1)).selectPreferredUpstreamType(any());
+        verify(mUpstreamNetworkMonitor, times(1)).getCurrentPreferredUpstream();
         verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(upstreamState.network);
     }
 
@@ -872,8 +883,7 @@
 
         // Then 464xlat comes up
         upstreamState = buildMobile464xlatUpstreamState();
-        when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any()))
-                .thenReturn(upstreamState);
+        initTetheringUpstream(upstreamState);
 
         // Upstream LinkProperties changed: UpstreamNetworkMonitor sends EVENT_ON_LINKPROPERTIES.
         mTetheringDependencies.mUpstreamNetworkMonitorMasterSM.sendMessage(
@@ -1344,9 +1354,7 @@
         callback.expectOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
         // 2. Enable wifi tethering.
         UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
-        when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
-        when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any()))
-                .thenReturn(upstreamState);
+        initTetheringUpstream(upstreamState);
         when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
         mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
         mLooper.dispatchAll();
@@ -1723,7 +1731,7 @@
         final Tethering.TetherMasterSM stateMachine = (Tethering.TetherMasterSM)
                 mTetheringDependencies.mUpstreamNetworkMonitorMasterSM;
         final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
-        when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())).thenReturn(upstreamState);
+        initTetheringUpstream(upstreamState);
         stateMachine.chooseUpstreamType(true);
 
         verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(eq(upstreamState.network));
@@ -1735,7 +1743,7 @@
         final Tethering.TetherMasterSM stateMachine = (Tethering.TetherMasterSM)
                 mTetheringDependencies.mUpstreamNetworkMonitorMasterSM;
         final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
-        when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())).thenReturn(upstreamState);
+        initTetheringUpstream(upstreamState);
         stateMachine.chooseUpstreamType(true);
 
         stateMachine.handleUpstreamNetworkMonitorCallback(EVENT_ON_CAPABILITIES, upstreamState);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
index 0bf8993..aee4afb 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java
@@ -212,12 +212,12 @@
         if (mDestroyed || mResponseCallback == null) {
             return;
         }
-        if (!mImeInputViewStarted && mPreviousResponseIsNotEmpty) {
-            // 1. if previous response is not empty, and IME just become invisible, then send
-            // empty response to make sure existing responses don't stick around on the IME.
+        if (!mImeInputStarted && mPreviousResponseIsNotEmpty) {
+            // 1. if previous response is not empty, and IME is just disconnected from the view,
+            // then send empty response to make sure existing responses don't stick around.
             // Although the inline suggestions should disappear when IME hides which removes them
-            // from the view hierarchy, but we still send an empty response to be extra safe.
-
+            // from the view hierarchy, but we still send an empty response to indicate that the
+            // previous suggestions are invalid now.
             if (sVerbose) Slog.v(TAG, "Send empty inline response");
             updateResponseToImeUncheckLocked(new InlineSuggestionsResponse(Collections.EMPTY_LIST));
             mPreviousResponseIsNotEmpty = false;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index ef17d13..1d3ab9b 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2581,7 +2581,6 @@
                     if (sVerbose) Slog.v(TAG, "Exiting view " + id);
                     mUi.hideFillUi(this);
                     hideAugmentedAutofillLocked(viewState);
-                    mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
                     mCurrentViewId = null;
                 }
                 break;
@@ -2655,6 +2654,9 @@
                 if (sVerbose) {
                     Slog.v(TAG, "ignoring autofilled change on id " + id);
                 }
+                // TODO(b/156099633): remove this once framework gets out of business of resending
+                // inline suggestions when IME visibility changes.
+                mInlineSessionController.hideInlineSuggestionsUiLocked(viewState.id);
                 viewState.resetState(ViewState.STATE_CHANGED);
                 return;
             } else if ((viewState.id.equals(this.mCurrentViewId))
@@ -3348,7 +3350,9 @@
                 if (generateEvent) {
                     mService.logDatasetSelected(dataset.getId(), id, mClientState);
                 }
-
+                if (mCurrentViewId != null) {
+                    mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId);
+                }
                 autoFillApp(dataset);
                 return;
             }
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index 089eeb2..8cc0de4 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -57,7 +57,6 @@
             @NonNull AutoFillUI.AutoFillUiCallback client, @NonNull Runnable onErrorCallback,
             @Nullable RemoteInlineSuggestionRenderService remoteRenderService) {
         final BiConsumer<Dataset, Integer> onClickFactory = (dataset, datasetIndex) -> {
-            client.requestHideFillUi(autofillId);
             client.authenticate(response.getRequestId(),
                     datasetIndex, response.getAuthentication(), response.getClientState(),
                     /* authenticateInline= */ true);
@@ -85,7 +84,6 @@
         final Consumer<IntentSender> intentSenderConsumer = (intentSender) ->
                 client.startIntentSender(intentSender, new Intent());
         final BiConsumer<Dataset, Integer> onClickFactory = (dataset, datasetIndex) -> {
-            client.requestHideFillUi(autofillId);
             client.fill(requestId, datasetIndex, dataset);
         };
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d914bda..930f124 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -13597,7 +13597,9 @@
                         pw.print(" unmapped + ");
                         pw.print(stringifyKBSize(ionPool));
                         pw.println(" pools)");
-                kernelUsed += ionUnmapped;
+                // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being
+                // set on ION VMAs, therefore consider the entire ION heap as used kernel memory
+                kernelUsed += ionHeap;
             }
             final long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)
                     - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
@@ -14403,7 +14405,9 @@
             memInfoBuilder.append("       ION: ");
             memInfoBuilder.append(stringifyKBSize(ionHeap + ionPool));
             memInfoBuilder.append("\n");
-            kernelUsed += ionUnmapped;
+            // Note: mapped ION memory is not accounted in PSS due to VM_PFNMAP flag being
+            // set on ION VMAs, therefore consider the entire ION heap as used kernel memory
+            kernelUsed += ionHeap;
         }
         memInfoBuilder.append("  Used RAM: ");
         memInfoBuilder.append(stringifyKBSize(
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 17baead..8068e37 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7369,10 +7369,32 @@
                 return false;
             }
             boolean suppress = false;
-            if (resolvedStream != AudioSystem.STREAM_MUSIC && mController != null) {
+            // Intended behavior:
+            // 1/ if the stream is not the default UI stream, do not suppress (as it is not involved
+            //    in bringing up the UI)
+            // 2/ if the resolved and default stream is MUSIC, and media is playing, do not suppress
+            // 3/ otherwise suppress the first adjustments that occur during the "long press
+            //    timeout" interval. Note this is true regardless of whether this is a "real long
+            //    press" (where the user keeps pressing on the volume button), or repeated single
+            //    presses (here we don't know if we are in a real long press, or repeated fast
+            //    button presses).
+            //    Once the long press timeout occurs (mNextLongPress reset to 0), do not suppress.
+            // Example: for a default and resolved stream of MUSIC, this allows modifying rapidly
+            // the volume when media is playing (whether by long press or repeated individual
+            // presses), or to bring up the volume UI when media is not playing, in order to make
+            // another change (e.g. switch ringer modes) without changing media volume.
+            if (resolvedStream == DEFAULT_VOL_STREAM_NO_PLAYBACK && mController != null) {
+                // never suppress media vol adjustement during media playback
+                if (resolvedStream == AudioSystem.STREAM_MUSIC
+                        && AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, mLongPressTimeout))
+                {
+                    // media is playing, adjust the volume right away
+                    return false;
+                }
+
                 final long now = SystemClock.uptimeMillis();
                 if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) {
-                    // ui will become visible
+                    // UI is not visible yet, adjustment is ignored
                     if (mNextLongPress < now) {
                         mNextLongPress = now + mLongPressTimeout;
                     }
diff --git a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
index 75fd7dc..927fcfb 100644
--- a/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/gnss/GnssVisibilityControl.java
@@ -630,7 +630,7 @@
         }
 
         Notification.Builder builder = new Notification.Builder(mContext,
-                SystemNotificationChannels.NETWORK_ALERTS)
+                SystemNotificationChannels.NETWORK_STATUS)
                 .setSmallIcon(R.drawable.stat_sys_gps_on)
                 .setCategory(Notification.CATEGORY_SYSTEM)
                 .setVisibility(Notification.VISIBILITY_SECRET)
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 5e865e7..d7bd794 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -584,7 +584,8 @@
         mAllRouterRecords.put(binder, routerRecord);
 
         userRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::notifyRoutesToRouter, userRecord.mHandler, router));
+                obtainMessage(UserHandler::notifyRoutesToRouter,
+                        userRecord.mHandler, routerRecord));
     }
 
     private void unregisterRouter2Locked(@NonNull IMediaRouter2 router, boolean died) {
@@ -1775,16 +1776,36 @@
             }
         }
 
-        private void notifyRoutesToRouter(@NonNull IMediaRouter2 router) {
+        private void notifyRoutesToRouter(@NonNull RouterRecord routerRecord) {
             List<MediaRoute2Info> routes = new ArrayList<>();
+
+            MediaRoute2ProviderInfo systemProviderInfo = null;
             for (MediaRoute2ProviderInfo providerInfo : mLastProviderInfos) {
+                // TODO: Create MediaRoute2ProviderInfo#isSystemProvider()
+                if (TextUtils.equals(providerInfo.getUniqueId(), mSystemProvider.getUniqueId())) {
+                    // Adding routes from system provider will be handled below, so skip it here.
+                    systemProviderInfo = providerInfo;
+                    continue;
+                }
                 routes.addAll(providerInfo.getRoutes());
             }
+
+            if (routerRecord.mHasModifyAudioRoutingPermission) {
+                if (systemProviderInfo != null) {
+                    routes.addAll(systemProviderInfo.getRoutes());
+                } else {
+                    // This shouldn't happen.
+                    Slog.w(TAG, "notifyRoutesToRouter: System route provider not found.");
+                }
+            } else {
+                routes.add(mSystemProvider.getDefaultRoute());
+            }
+
             if (routes.size() == 0) {
                 return;
             }
             try {
-                router.notifyRoutesAdded(routes);
+                routerRecord.mRouter.notifyRoutesAdded(routes);
             } catch (RemoteException ex) {
                 Slog.w(TAG, "Failed to notify all routes. Router probably died.", ex);
             }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 7af0787..1741aa7 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -395,7 +395,6 @@
 
     private boolean mDataLoaderFinished = false;
 
-    // TODO(b/146080380): merge file list with Callback installation.
     private IncrementalFileStorages mIncrementalFileStorages;
 
     private static final FileFilter sAddedApkFilter = new FileFilter() {
@@ -2698,6 +2697,7 @@
 
     /**
      * Makes sure files are present in staging location.
+     * @return if the image is ready for installation
      */
     @GuardedBy("mLock")
     private boolean prepareDataLoaderLocked()
@@ -2709,6 +2709,17 @@
             return true;
         }
 
+        // Retrying commit.
+        if (mIncrementalFileStorages != null) {
+            try {
+                mIncrementalFileStorages.startLoading();
+            } catch (IOException e) {
+                throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE, e.getMessage(),
+                        e.getCause());
+            }
+            return false;
+        }
+
         final List<InstallationFileParcel> addedFiles = new ArrayList<>();
         final List<String> removedFiles = new ArrayList<>();
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 3ead72c..7959461 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -608,6 +608,12 @@
     private static final long DEFAULT_VERIFICATION_TIMEOUT = 10 * 1000;
 
     /**
+     * The default maximum time to wait for the integrity verification to return in
+     * milliseconds.
+     */
+    private static final long DEFAULT_INTEGRITY_VERIFICATION_TIMEOUT = 30 * 1000;
+
+    /**
      * Timeout duration in milliseconds for enabling package rollback. If we fail to enable
      * rollback within that period, the install will proceed without rollback enabled.
      *
@@ -13838,6 +13844,19 @@
     }
 
     /**
+     * Get the integrity verification timeout.
+     *
+     * @return verification timeout in milliseconds
+     */
+    private long getIntegrityVerificationTimeout() {
+        long timeout = Global.getLong(mContext.getContentResolver(),
+                Global.APP_INTEGRITY_VERIFICATION_TIMEOUT, DEFAULT_INTEGRITY_VERIFICATION_TIMEOUT);
+        // The setting can be used to increase the timeout but not decrease it, since that is
+        // equivalent to disabling the integrity component.
+        return Math.max(timeout, DEFAULT_INTEGRITY_VERIFICATION_TIMEOUT);
+    }
+
+    /**
      * Get the default verification agent response code.
      *
      * @return default verification response code
@@ -15032,8 +15051,7 @@
                             final Message msg =
                                     mHandler.obtainMessage(CHECK_PENDING_INTEGRITY_VERIFICATION);
                             msg.arg1 = verificationId;
-                            // TODO: do we want to use the same timeout?
-                            mHandler.sendMessageDelayed(msg, getVerificationTimeout());
+                            mHandler.sendMessageDelayed(msg, getIntegrityVerificationTimeout());
                         }
                     }, /* scheduler= */ null,
                     /* initialCode= */ 0,
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index bc94528..0dc4d13 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -3172,7 +3172,7 @@
             metadata = (streamingVersion == 0) ? Metadata.forDataOnlyStreaming(fileId)
                     : Metadata.forStreaming(fileId);
             try {
-                if (V4Signature.readFrom(signature) == null) {
+                if ((signature.length > 0) && (V4Signature.readFrom(signature) == null)) {
                     getErrPrintWriter().println("V4 signature is invalid in: " + arg);
                     return 1;
                 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 63952b0..31fbaff 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2743,7 +2743,7 @@
                     mContext, 0,
                     Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER),
                             mContext.getText(com.android.internal.R.string.chooser_wallpaper)),
-                    0, null, new UserHandle(serviceUserId)));
+                    PendingIntent.FLAG_IMMUTABLE, null, new UserHandle(serviceUserId)));
             if (!mContext.bindServiceAsUser(intent, newConn,
                     Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
                             | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 131e449..d177c12 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4785,16 +4785,7 @@
         if (!task.hasChild(this)) {
             throw new IllegalStateException("Activity not found in its task");
         }
-        final ActivityRecord activityAbove = task.getActivityAbove(this);
-        if (activityAbove == null) {
-            // It's the topmost activity in the task - should become resumed now
-            return true;
-        }
-        // Check if activity above is finishing now and this one becomes the topmost in task.
-        if (activityAbove.finishing) {
-            return true;
-        }
-        return false;
+        return task.topRunningActivity() == this;
     }
 
     void handleAlreadyVisible() {
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index dca0860..cb2d98c 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -1307,17 +1307,8 @@
     /**
      * Make sure that all activities that need to be visible in the stack (that is, they
      * currently can be seen by the user) actually are and update their configuration.
-     * @param starting The top most activity in the task.
-     *                 The activity is either starting or resuming.
-     *                 Caller should ensure starting activity is visible.
-     * @param preserveWindows Flag indicating whether windows should be preserved when updating
-     *                        configuration in {@link mEnsureActivitiesVisibleHelper}.
-     * @param configChanges Parts of the configuration that changed for this activity for evaluating
-     *                      if the screen should be frozen as part of
-     *                      {@link mEnsureActivitiesVisibleHelper}.
-     *
      */
-    void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
+    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
             boolean preserveWindows) {
         ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */);
     }
@@ -1326,19 +1317,9 @@
      * Ensure visibility with an option to also update the configuration of visible activities.
      * @see #ensureActivitiesVisible(ActivityRecord, int, boolean)
      * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
-     * @param starting The top most activity in the task.
-     *                 The activity is either starting or resuming.
-     *                 Caller should ensure starting activity is visible.
-     * @param notifyClients Flag indicating whether the visibility updates should be sent to the
-     *                      clients in {@link mEnsureActivitiesVisibleHelper}.
-     * @param preserveWindows Flag indicating whether windows should be preserved when updating
-     *                        configuration in {@link mEnsureActivitiesVisibleHelper}.
-     * @param configChanges Parts of the configuration that changed for this activity for evaluating
-     *                      if the screen should be frozen as part of
-     *                      {@link mEnsureActivitiesVisibleHelper}.
      */
     // TODO: Should be re-worked based on the fact that each task as a stack in most cases.
-    void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges,
+    void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
             boolean preserveWindows, boolean notifyClients) {
         mTopActivityOccludesKeyguard = false;
         mTopDismissingKeyguardActivity = null;
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index bcdd6e3..79e8ee3 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1539,10 +1539,7 @@
      *
      * Note: This method should only be called from {@link #startActivityUnchecked}.
      */
-
-    // TODO(b/152429287): Make it easier to exercise code paths through startActivityInner
-    @VisibleForTesting
-    int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
+    private int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
             IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
             int startFlags, boolean doResume, ActivityOptions options, Task inTask,
             boolean restrictedBgActivity) {
@@ -1663,10 +1660,7 @@
                 // Also, we don't want to resume activities in a task that currently has an overlay
                 // as the starting activity just needs to be in the visible paused state until the
                 // over is removed.
-                // Passing {@code null} as the start parameter ensures all activities are made
-                // visible.
-                mTargetStack.ensureActivitiesVisible(null /* starting */,
-                        0 /* configChanges */, !PRESERVE_WINDOWS);
+                mTargetStack.ensureActivitiesVisible(mStartActivity, 0, !PRESERVE_WINDOWS);
                 // Go ahead and tell window manager to execute app transition for this activity
                 // since the app transition will not be triggered through the resume channel.
                 mTargetStack.getDisplay().mDisplayContent.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/AnimationAdapter.java b/services/core/java/com/android/server/wm/AnimationAdapter.java
index 0519b80..529c4f6 100644
--- a/services/core/java/com/android/server/wm/AnimationAdapter.java
+++ b/services/core/java/com/android/server/wm/AnimationAdapter.java
@@ -85,4 +85,25 @@
     }
 
     void dumpDebug(ProtoOutputStream proto);
+
+    /**
+     * Gets called when the animation is about to finish and gives the client the opportunity to
+     * defer finishing the animation, i.e. it keeps the leash around until the client calls
+     * endDeferFinishCallback.
+     * <p>
+     * This has the same effect as
+     * {@link com.android.server.wm.SurfaceAnimator.Animatable#shouldDeferAnimationFinish(Runnable)}
+     * . The later will be evaluated first and has precedence over this method if it returns true,
+     * which means that if the {@link com.android.server.wm.SurfaceAnimator.Animatable} requests to
+     * defer its finish, this method won't be called so this adapter will never have access to the
+     * finish callback. On the other hand, if the
+     * {@link com.android.server.wm.SurfaceAnimator.Animatable}, doesn't request to defer, this
+     * {@link AnimationAdapter} is responsible for ending the animation.
+     *
+     * @param endDeferFinishCallback The callback to call when defer finishing should be ended.
+     * @return Whether the client would like to defer the animation finish.
+     */
+    default boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e31eaf7..6655e92 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -499,6 +499,8 @@
      */
     ActivityRecord mFixedRotationLaunchingApp;
 
+    FixedRotationAnimationController mFixedRotationAnimationController;
+
     final FixedRotationTransitionListener mFixedRotationTransitionListener =
             new FixedRotationTransitionListener();
 
@@ -1528,6 +1530,11 @@
     }
 
     private void startFixedRotationTransform(WindowToken token, int rotation) {
+        if (mFixedRotationAnimationController == null) {
+            mFixedRotationAnimationController = new FixedRotationAnimationController(
+                    this);
+        }
+        mFixedRotationAnimationController.hide(rotation);
         mTmpConfiguration.unset();
         final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation);
         final WmDisplayCutout cutout = calculateDisplayCutoutForRotation(rotation);
@@ -1549,6 +1556,13 @@
         }
     }
 
+    void finishFixedRotationAnimation() {
+        if (mFixedRotationAnimationController != null
+                && mFixedRotationAnimationController.show()) {
+            mFixedRotationAnimationController = null;
+        }
+    }
+
     /**
      * Update rotation of the display.
      *
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index ebfe70c..c3f9061 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -560,6 +560,7 @@
         }, true /* traverseTopToBottom */);
         mSeamlessRotationCount = 0;
         mRotatingSeamlessly = false;
+        mDisplayContent.finishFixedRotationAnimation();
     }
 
     private void prepareSeamlessRotation() {
@@ -646,6 +647,7 @@
                     "Performing post-rotate rotation after seamless rotation");
             // Finish seamless rotation.
             mRotatingSeamlessly = false;
+            mDisplayContent.finishFixedRotationAnimation();
 
             updateRotationAndSendNewConfigIfChanged();
         }
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index c4e03f5..c92de2b 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -21,7 +21,6 @@
 import static com.android.server.wm.ActivityStack.TAG_VISIBILITY;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY;
 
-import android.annotation.Nullable;
 import android.util.Slog;
 
 import com.android.internal.util.function.pooled.PooledConsumer;
@@ -43,16 +42,6 @@
         mContiner = container;
     }
 
-    /**
-     * Update all attributes except {@link mContiner} to use in subsequent calculations.
-     *
-     * @param starting The activity that is being started
-     * @param configChanges Parts of the configuration that changed for this activity for evaluating
-     *                      if the screen should be frozen.
-     * @param preserveWindows Flag indicating whether windows should be preserved when updating.
-     * @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
-     *                      be sent to the clients.
-     */
     void reset(ActivityRecord starting, int configChanges, boolean preserveWindows,
             boolean notifyClients) {
         mStarting = starting;
@@ -71,17 +60,8 @@
      * Ensure visibility with an option to also update the configuration of visible activities.
      * @see ActivityStack#ensureActivitiesVisible(ActivityRecord, int, boolean)
      * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean)
-     * @param starting The top most activity in the task.
-     *                 The activity is either starting or resuming.
-     *                 Caller should ensure starting activity is visible.
-     *
-     * @param configChanges Parts of the configuration that changed for this activity for evaluating
-     *                      if the screen should be frozen.
-     * @param preserveWindows Flag indicating whether windows should be preserved when updating.
-     * @param notifyClients Flag indicating whether the configuration and visibility changes shoulc
-     *                      be sent to the clients.
      */
-    void process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows,
+    void process(ActivityRecord starting, int configChanges, boolean preserveWindows,
             boolean notifyClients) {
         reset(starting, configChanges, preserveWindows, notifyClients);
 
diff --git a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
new file mode 100644
index 0000000..7aca637
--- /dev/null
+++ b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.AnimationSpecProto.WINDOW;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
+import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
+
+import android.content.res.Configuration;
+import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Transformation;
+
+import com.android.internal.R;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Controller to fade out and in system ui when applying a fixed rotation transform to a window
+ * token.
+ *
+ * The system bars will be fade out when the fixed rotation transform starts and will be fade in
+ * once all surfaces have been rotated.
+ */
+public class FixedRotationAnimationController {
+
+    private final WindowManagerService mWmService;
+    private boolean mShowRequested = true;
+    private int mTargetRotation = Configuration.ORIENTATION_UNDEFINED;
+    private final ArrayList<WindowState> mAnimatedWindowStates = new ArrayList<>(2);
+    private final Runnable[] mDeferredFinishCallbacks;
+
+    public FixedRotationAnimationController(DisplayContent displayContent) {
+        mWmService = displayContent.mWmService;
+        addAnimatedWindow(displayContent.getDisplayPolicy().getStatusBar());
+        addAnimatedWindow(displayContent.getDisplayPolicy().getNavigationBar());
+        mDeferredFinishCallbacks = new Runnable[mAnimatedWindowStates.size()];
+    }
+
+    private void addAnimatedWindow(WindowState windowState) {
+        if (windowState != null) {
+            mAnimatedWindowStates.add(windowState);
+        }
+    }
+
+    /**
+     * Show the previously hidden {@link WindowToken} if their surfaces have already been rotated.
+     *
+     * @return True if the show animation has been started, in which case the caller no longer needs
+     * this {@link FixedRotationAnimationController}.
+     */
+    boolean show() {
+        if (!mShowRequested && readyToShow()) {
+            mShowRequested = true;
+            for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
+                WindowState windowState = mAnimatedWindowStates.get(i);
+                fadeWindowToken(true, windowState.getParent(), i);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    void hide(int targetRotation) {
+        mTargetRotation = targetRotation;
+        if (mShowRequested) {
+            mShowRequested = false;
+            for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
+                WindowState windowState = mAnimatedWindowStates.get(i);
+                fadeWindowToken(false /* show */, windowState.getParent(), i);
+            }
+        }
+    }
+
+    void cancel() {
+        for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
+            WindowState windowState = mAnimatedWindowStates.get(i);
+            mShowRequested = true;
+            fadeWindowToken(true /* show */, windowState.getParent(), i);
+        }
+    }
+
+    private void fadeWindowToken(boolean show, WindowContainer<WindowToken> windowToken,
+            int index) {
+        Animation animation = AnimationUtils.loadAnimation(mWmService.mContext,
+                show ? R.anim.fade_in : R.anim.fade_out);
+        LocalAnimationAdapter.AnimationSpec windowAnimationSpec = createAnimationSpec(animation);
+
+        FixedRotationAnimationAdapter animationAdapter = new FixedRotationAnimationAdapter(
+                windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, index);
+
+        // We deferred the end of the animation when hiding the token, so we need to end it now that
+        // it's shown again.
+        SurfaceAnimator.OnAnimationFinishedCallback finishedCallback = show ? (t, r) -> {
+            if (mDeferredFinishCallbacks[index] != null) {
+                mDeferredFinishCallbacks[index].run();
+                mDeferredFinishCallbacks[index] = null;
+            }
+        } : null;
+        windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter,
+                mShowRequested, ANIMATION_TYPE_FIXED_TRANSFORM, finishedCallback);
+    }
+
+    /**
+     * Check if all the mAnimatedWindowState's surfaces have been rotated to the
+     * mTargetRotation.
+     */
+    private boolean readyToShow() {
+        for (int i = mAnimatedWindowStates.size() - 1; i >= 0; i--) {
+            WindowState windowState = mAnimatedWindowStates.get(i);
+            if (windowState.getConfiguration().windowConfiguration.getRotation()
+                    != mTargetRotation || windowState.mPendingSeamlessRotate != null) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    private LocalAnimationAdapter.AnimationSpec createAnimationSpec(Animation animation) {
+        return new LocalAnimationAdapter.AnimationSpec() {
+
+            Transformation mTransformation = new Transformation();
+
+            @Override
+            public boolean getShowWallpaper() {
+                return true;
+            }
+
+            @Override
+            public long getDuration() {
+                return animation.getDuration();
+            }
+
+            @Override
+            public void apply(SurfaceControl.Transaction t, SurfaceControl leash,
+                    long currentPlayTime) {
+                mTransformation.clear();
+                animation.getTransformation(currentPlayTime, mTransformation);
+                t.setAlpha(leash, mTransformation.getAlpha());
+            }
+
+            @Override
+            public void dump(PrintWriter pw, String prefix) {
+                pw.print(prefix);
+                pw.println(animation);
+            }
+
+            @Override
+            public void dumpDebugInner(ProtoOutputStream proto) {
+                final long token = proto.start(WINDOW);
+                proto.write(ANIMATION, animation.toString());
+                proto.end(token);
+            }
+        };
+    }
+
+    private class FixedRotationAnimationAdapter extends LocalAnimationAdapter {
+        private final boolean mShow;
+        private final int mIndex;
+
+        FixedRotationAnimationAdapter(AnimationSpec windowAnimationSpec,
+                SurfaceAnimationRunner surfaceAnimationRunner, boolean show, int index) {
+            super(windowAnimationSpec, surfaceAnimationRunner);
+            mShow = show;
+            mIndex = index;
+        }
+
+        @Override
+        public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
+            // We defer the end of the hide animation to ensure the tokens stay hidden until
+            // we show them again.
+            if (!mShow) {
+                mDeferredFinishCallbacks[mIndex] = endDeferFinishCallback;
+                return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 18e32c0..0143eb1 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -109,7 +109,10 @@
                         animationFinishCallback.onAnimationFinished(type, anim);
                     }
                 };
-                if (!mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish)) {
+                // If both the Animatable and AnimationAdapter requests to be deferred, only the
+                // first one will be called.
+                if (!(mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish)
+                        || anim.shouldDeferAnimationFinish(resetAndInvokeFinish))) {
                     resetAndInvokeFinish.run();
                 }
             }
@@ -486,6 +489,12 @@
     static final int ANIMATION_TYPE_INSETS_CONTROL = 1 << 5;
 
     /**
+     * Animation when a fixed rotation transform is applied to a window token.
+     * @hide
+     */
+    static final int ANIMATION_TYPE_FIXED_TRANSFORM = 1 << 6;
+
+    /**
      * Bitmask to include all animation types. This is NOT an {@link AnimationType}
      * @hide
      */
@@ -502,7 +511,8 @@
             ANIMATION_TYPE_DIMMER,
             ANIMATION_TYPE_RECENTS,
             ANIMATION_TYPE_WINDOW_ANIMATION,
-            ANIMATION_TYPE_INSETS_CONTROL
+            ANIMATION_TYPE_INSETS_CONTROL,
+            ANIMATION_TYPE_FIXED_TRANSFORM
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface AnimationType {}
@@ -592,6 +602,12 @@
          * Gets called when the animation is about to finish and gives the client the opportunity to
          * defer finishing the animation, i.e. it keeps the leash around until the client calls
          * {@link #cancelAnimation}.
+         * <p>
+         * {@link AnimationAdapter} has a similar method which is called only if this method returns
+         * false. This mean that if both this {@link Animatable} and the {@link AnimationAdapter}
+         * request to be deferred, this method is the sole responsible to call
+         * endDeferFinishCallback. On the other hand, the animation finish might still be deferred
+         * if this method return false and the one from the {@link AnimationAdapter} returns true.
          *
          * @param endDeferFinishCallback The callback to call when defer finishing should be ended.
          * @return Whether the client would like to defer the animation finish.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4c1d6f3..91749a6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8082,6 +8082,7 @@
         t.setInputWindowInfo(surface, h);
         t.apply();
         t.close();
+        surface.release();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index c4cb4b5..707a789 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -138,6 +138,13 @@
                             Slog.e(TAG, "Attempt to operate on detached container: " + wc);
                             continue;
                         }
+                        // Make sure we add to the syncSet before performing
+                        // operations so we don't end up splitting effects between the WM
+                        // pending transaction and the BLASTSync transaction.
+                        if (syncId >= 0) {
+                            mBLASTSyncEngine.addToSyncSet(syncId, wc);
+                        }
+
                         int containerEffect = applyWindowContainerChange(wc, entry.getValue());
                         effects |= containerEffect;
 
@@ -146,9 +153,6 @@
                                 && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
                             haveConfigChanges.add(wc);
                         }
-                        if (syncId >= 0) {
-                            mBLASTSyncEngine.addToSyncSet(syncId, wc);
-                        }
                     }
                     // Hierarchy changes
                     final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 768f89e..8739bad4 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -642,6 +642,9 @@
         final int originalRotation = getWindowConfiguration().getRotation();
         onConfigurationChanged(parent.getConfiguration());
         onCancelFixedRotationTransform(originalRotation);
+        if (mDisplayContent.mFixedRotationAnimationController != null) {
+            mDisplayContent.mFixedRotationAnimationController.cancel();
+        }
     }
 
     /**
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 6cf8133..e904645 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -18,6 +18,7 @@
 #define LOG_TAG "PackageManagerShellCommandDataLoader-jni"
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/no_destructor.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
 #include <core_jni_helpers.h>
@@ -65,6 +66,7 @@
 static constexpr MagicType INCR = 0x52434e49; // BE INCR
 
 static constexpr auto PollTimeoutMs = 5000;
+static constexpr auto TraceTagCheckInterval = 1s;
 
 struct JniIds {
     jclass packageManagerShellCommandDataLoader;
@@ -337,9 +339,47 @@
     return env;
 }
 
-class PackageManagerShellCommandDataLoaderDataLoader : public android::dataloader::DataLoader {
+class PMSCDataLoader;
+
+struct OnTraceChanged {
+    OnTraceChanged();
+    ~OnTraceChanged() {
+        mRunning = false;
+        mChecker.join();
+    }
+
+    void registerCallback(PMSCDataLoader* callback) {
+        std::unique_lock lock(mMutex);
+        mCallbacks.insert(callback);
+    }
+
+    void unregisterCallback(PMSCDataLoader* callback) {
+        std::unique_lock lock(mMutex);
+        mCallbacks.erase(callback);
+    }
+
+private:
+    std::mutex mMutex;
+    std::unordered_set<PMSCDataLoader*> mCallbacks;
+    std::atomic<bool> mRunning{true};
+    std::thread mChecker;
+};
+
+static OnTraceChanged& onTraceChanged() {
+    static android::base::NoDestructor<OnTraceChanged> instance;
+    return *instance;
+}
+
+class PMSCDataLoader : public android::dataloader::DataLoader {
 public:
-    PackageManagerShellCommandDataLoaderDataLoader(JavaVM* jvm) : mJvm(jvm) { CHECK(mJvm); }
+    PMSCDataLoader(JavaVM* jvm) : mJvm(jvm) { CHECK(mJvm); }
+    ~PMSCDataLoader() { onTraceChanged().unregisterCallback(this); }
+
+    void updateReadLogsState(const bool enabled) {
+        if (enabled != mReadLogsEnabled.exchange(enabled)) {
+            mIfs->setParams({.readLogsEnabled = enabled});
+        }
+    }
 
 private:
     // Lifecycle.
@@ -353,7 +393,8 @@
         mArgs = params.arguments();
         mIfs = ifs;
         mStatusListener = statusListener;
-        mIfs->setParams({.readLogsEnabled = true});
+        updateReadLogsState(atrace_is_tag_enabled(ATRACE_TAG));
+        onTraceChanged().registerCallback(this);
         return true;
     }
     bool onStart() final { return true; }
@@ -365,6 +406,7 @@
         }
     }
     void onDestroy() final {
+        onTraceChanged().unregisterCallback(this);
         // Make sure the receiver thread stopped.
         CHECK(!mReceiverThread.joinable());
     }
@@ -757,10 +799,28 @@
     android::base::unique_fd mEventFd;
     std::thread mReceiverThread;
     std::atomic<bool> mStopReceiving = false;
+    std::atomic<bool> mReadLogsEnabled = false;
     /** Tracks which files have been requested */
     std::unordered_set<FileIdx> mRequestedFiles;
 };
 
+OnTraceChanged::OnTraceChanged() {
+    mChecker = std::thread([this]() {
+        bool oldTrace = atrace_is_tag_enabled(ATRACE_TAG);
+        while (mRunning) {
+            bool newTrace = atrace_is_tag_enabled(ATRACE_TAG);
+            if (oldTrace != newTrace) {
+                std::unique_lock lock(mMutex);
+                for (auto&& callback : mCallbacks) {
+                    callback->updateReadLogsState(newTrace);
+                }
+            }
+            oldTrace = newTrace;
+            std::this_thread::sleep_for(TraceTagCheckInterval);
+        }
+    });
+}
+
 BlockHeader readHeader(std::span<uint8_t>& data) {
     BlockHeader header;
     if (data.size() < sizeof(header)) {
@@ -794,7 +854,7 @@
             [](auto jvm, const auto& params) -> android::dataloader::DataLoaderPtr {
                 if (params.type() == DATA_LOADER_TYPE_INCREMENTAL) {
                     // This DataLoader only supports incremental installations.
-                    return std::make_unique<PackageManagerShellCommandDataLoaderDataLoader>(jvm);
+                    return std::make_unique<PMSCDataLoader>(jvm);
                 }
                 return {};
             });
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index a9dc92f..78439db 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -748,7 +748,7 @@
         return -EINVAL;
     }
 
-    LOG(INFO) << "Removing bind point " << target;
+    LOG(INFO) << "Removing bind point " << target << " for storage " << storage;
 
     // Here we should only look up by the exact target, not by a subdirectory of any existing mount,
     // otherwise there's a chance to unmount something completely unrelated
@@ -1807,6 +1807,8 @@
         targetStatus = mTargetStatus;
     }
 
+    LOG(DEBUG) << "fsmStep: " << mId << ": " << currentStatus << " -> " << targetStatus;
+
     if (currentStatus == targetStatus) {
         return true;
     }
@@ -1868,8 +1870,8 @@
         listener = mListener;
 
         if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE) {
-            // For unavailable, reset target status.
-            setTargetStatusLocked(IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE);
+            // For unavailable, unbind from DataLoader to ensure proper re-commit.
+            setTargetStatusLocked(IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
         }
     }
 
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 325218d..2e4625c 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -677,10 +677,10 @@
     mDataLoaderManager->bindToDataLoaderSuccess();
     mDataLoaderManager->getDataLoaderSuccess();
     EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(2);
-    EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(1);
+    EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(2);
     EXPECT_CALL(*mDataLoader, create(_, _, _, _)).Times(2);
     EXPECT_CALL(*mDataLoader, start(_)).Times(0);
-    EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
+    EXPECT_CALL(*mDataLoader, destroy(_)).Times(2);
     EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
     EXPECT_CALL(*mLooper, addFd(MockIncFs::kPendingReadsFd, _, _, _, _)).Times(1);
     EXPECT_CALL(*mLooper, removeFd(MockIncFs::kPendingReadsFd)).Times(1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 8cfe96f..2bc3478 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -488,6 +488,16 @@
     }
 
     @Test
+    public void testShouldMakeActive_nonTopVisible() {
+        ActivityRecord finishingActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        finishingActivity.finishing = true;
+        ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
+
+        assertEquals(false, mActivity.shouldMakeActive(null /* activeActivity */));
+    }
+
+    @Test
     public void testShouldResume_stackVisibility() {
         mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
         spyOn(mStack);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 02d1f9b..edc9756 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -49,7 +49,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -57,10 +56,10 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 
@@ -100,6 +99,7 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class ActivityStarterTests extends ActivityTestsBase {
+    private ActivityStarter mStarter;
     private ActivityStartController mController;
     private ActivityMetricsLogger mActivityMetricsLogger;
     private PackageManagerInternal mMockPackageManager;
@@ -127,6 +127,8 @@
         mController = mock(ActivityStartController.class);
         mActivityMetricsLogger = mock(ActivityMetricsLogger.class);
         clearInvocations(mActivityMetricsLogger);
+        mStarter = new ActivityStarter(mController, mService, mService.mStackSupervisor,
+                mock(ActivityStartInterceptor.class));
     }
 
     @Test
@@ -179,7 +181,6 @@
      * {@link ActivityStarter#execute} based on these preconditions and ensures the result matches
      * the expected. It is important to note that the method also checks side effects of the start,
      * such as ensuring {@link ActivityOptions#abort()} is called in the relevant scenarios.
-     *
      * @param preconditions A bitmask representing the preconditions for the launch
      * @param launchFlags The launch flags to be provided by the launch {@link Intent}.
      * @param expectedResult The expected result from the launch.
@@ -201,7 +202,7 @@
         final WindowProcessController wpc =
                 containsConditions(preconditions, PRECONDITION_NO_CALLER_APP)
                 ? null : new WindowProcessController(service, ai, null, 0, -1, null, listener);
-        doReturn(wpc).when(service).getProcessController(any());
+        doReturn(wpc).when(service).getProcessController(anyObject());
 
         final Intent intent = new Intent();
         intent.setFlags(launchFlags);
@@ -1033,46 +1034,4 @@
 
         verify(recentTasks, times(1)).add(any());
     }
-
-    @Test
-    public void testStartActivityInner_allSplitScreenPrimaryActivitiesVisible() {
-        // Given
-        final ActivityStarter starter = prepareStarter(0, false);
-
-        starter.setReason("testAllSplitScreenPrimaryActivitiesAreResumed");
-
-        final ActivityRecord targetRecord = new ActivityBuilder(mService).build();
-        targetRecord.setFocusable(false);
-        targetRecord.setVisibility(false);
-        final ActivityRecord sourceRecord = new ActivityBuilder(mService).build();
-
-        final ActivityStack stack = spy(
-                mRootWindowContainer.getDefaultTaskDisplayArea()
-                        .createStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
-                                /* onTop */true));
-
-        stack.addChild(targetRecord);
-
-        doReturn(stack).when(mRootWindowContainer)
-                .getLaunchStack(any(), any(), any(), anyBoolean(), any(), anyInt(), anyInt());
-
-        starter.mStartActivity = new ActivityBuilder(mService).build();
-
-        // When
-        starter.startActivityInner(
-                /* r */targetRecord,
-                /* sourceRecord */ sourceRecord,
-                /* voiceSession */null,
-                /* voiceInteractor */ null,
-                /* startFlags */ 0,
-                /* doResume */true,
-                /* options */null,
-                /* inTask */null,
-                /* restrictedBgActivity */false);
-
-        // Then
-        verify(stack).ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
-        verify(targetRecord).makeVisibleIfNeeded(null, true);
-        assertTrue(targetRecord.mVisibleRequested);
-    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 5c21853..9263d8a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -316,20 +316,16 @@
     }
 
     @Test
-    @FlakyTest(bugId = 130392471)
     public void testAddRemoveRace() {
         // There was once a race condition between adding and removing starting windows
+        final ActivityRecord appToken = mAppWindow.mActivityRecord;
         for (int i = 0; i < 1000; i++) {
-            final ActivityRecord appToken = createIsolatedTestActivityRecord();
-
             appToken.addStartingWindow(mPackageName,
                     android.R.style.Theme, null, "Test", 0, 0, 0, 0, null, true, true, false, true,
                     false, false);
             appToken.removeStartingWindow();
             waitUntilHandlersIdle();
             assertNoStartingWindow(appToken);
-
-            appToken.getParent().getParent().removeImmediately();
         }
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index ac95a81..7b23bfb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -57,6 +57,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 
@@ -1059,6 +1060,8 @@
     @Test
     public void testApplyTopFixedRotationTransform() {
         mWm.mIsFixedRotationTransformEnabled = true;
+        mDisplayContent.getDisplayPolicy().addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
+        mDisplayContent.getDisplayPolicy().addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
         final Configuration config90 = new Configuration();
         mDisplayContent.computeScreenConfiguration(config90, ROTATION_90);
 
@@ -1079,6 +1082,12 @@
                 ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
                 false /* forceUpdate */));
 
+        assertNotNull(mDisplayContent.mFixedRotationAnimationController);
+        assertTrue(mStatusBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
+                ANIMATION_TYPE_FIXED_TRANSFORM));
+        assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
+                ANIMATION_TYPE_FIXED_TRANSFORM));
+
         final Rect outFrame = new Rect();
         final Rect outInsets = new Rect();
         final Rect outStableInsets = new Rect();
@@ -1131,6 +1140,9 @@
         assertFalse(app.hasFixedRotationTransform());
         assertFalse(app2.hasFixedRotationTransform());
         assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
+
+        mDisplayContent.finishFixedRotationAnimation();
+        assertNull(mDisplayContent.mFixedRotationAnimationController);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index d605ab2..27c4e9b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -27,9 +27,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
-import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
-import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
@@ -40,7 +38,6 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -50,7 +47,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
-import static org.junit.Assume.assumeTrue;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.spy;
 import static org.testng.Assert.expectThrows;
@@ -64,18 +60,15 @@
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
 import android.view.InsetsState;
-import android.view.ViewRootImpl;
 import android.view.WindowInsets.Side;
 import android.view.WindowInsets.Type;
 import android.view.WindowManager;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.utils.WmDisplayCutout;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -116,12 +109,6 @@
         updateDisplayFrames();
     }
 
-    @After
-    public void tearDown() {
-        PolicyControl.setFilters("");
-        mWindow.getDisplayContent().mInputMethodTarget = null;
-    }
-
     public void setRotation(int rotation) {
         mRotation = rotation;
         updateDisplayFrames();
@@ -210,8 +197,6 @@
 
     @Test
     public void layoutWindowLw_fitStatusBars() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
         mWindow.mAttrs.setFitInsetsTypes(Type.statusBars());
         addWindow(mWindow);
 
@@ -228,8 +213,6 @@
 
     @Test
     public void layoutWindowLw_fitNavigationBars() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
         mWindow.mAttrs.setFitInsetsTypes(Type.navigationBars());
         addWindow(mWindow);
 
@@ -246,8 +229,6 @@
 
     @Test
     public void layoutWindowLw_fitAllSides() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
         mWindow.mAttrs.setFitInsetsSides(Side.all());
         addWindow(mWindow);
 
@@ -264,8 +245,6 @@
 
     @Test
     public void layoutWindowLw_fitTopOnly() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
         mWindow.mAttrs.setFitInsetsSides(Side.TOP);
         addWindow(mWindow);
 
@@ -282,8 +261,6 @@
 
     @Test
     public void layoutWindowLw_fitInsetsIgnoringVisibility() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
         final InsetsState state =
                 mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow);
         state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
@@ -304,8 +281,6 @@
 
     @Test
     public void layoutWindowLw_fitInsetsNotIgnoringVisibility() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
-
         final InsetsState state =
                 mDisplayContent.getInsetsPolicy().getInsetsForDispatch(mWindow);
         state.getSource(InsetsState.ITYPE_STATUS_BAR).setVisible(false);
@@ -326,7 +301,6 @@
 
     @Test
     public void layoutWindowLw_fitDisplayCutout() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL);
         addDisplayCutout();
 
         mWindow.mAttrs.setFitInsetsTypes(Type.displayCutout());
@@ -683,31 +657,6 @@
     }
 
     @Test
-    public void layoutWindowLw_withForwardInset_SoftInputAdjustResize() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_NONE);
-
-        mWindow.mAttrs.flags =
-                FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-        mWindow.mAttrs.setFitInsetsTypes(0 /* types */);
-        mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
-        addWindow(mWindow);
-
-        final int forwardedInsetBottom = 50;
-        mDisplayPolicy.setForwardedInsets(Insets.of(0, 0, 0, forwardedInsetBottom));
-        mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
-        mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
-        assertInsetBy(mWindow.getParentFrame(), 0, 0, 0, 0);
-        assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
-        assertInsetByTopBottom(mWindow.getContentFrameLw(),
-                STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT + forwardedInsetBottom);
-        assertInsetByTopBottom(mWindow.getVisibleFrameLw(),
-                STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT + forwardedInsetBottom);
-        assertInsetBy(mWindow.getDecorFrame(), 0, 0, 0, 0);
-        assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0);
-    }
-
-    @Test
     public void layoutWindowLw_withForwardInset_SoftInputAdjustNothing() {
         mWindow.mAttrs.flags =
                 FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
@@ -728,117 +677,6 @@
         assertInsetBy(mWindow.getDisplayFrameLw(), 0, 0, 0, 0);
     }
 
-    // TODO(b/118118435): remove after removing PolicyControl
-    @FlakyTest(bugId = 129711077)
-    @Test
-    public void layoutWindowLw_withImmersive_SoftInputAdjustResize() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
-
-        synchronized (mWm.mGlobalLock) {
-            mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
-            mWindow.mAttrs.flags = 0;
-            mWindow.mAttrs.systemUiVisibility =
-                    SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                            | SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_HIDE_NAVIGATION;
-
-            addWindow(mWindow);
-
-            mWindow.getDisplayContent().mInputMethodTarget = mWindow;
-            mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
-            mFrames.mContent.bottom = mFrames.mVoiceContent.bottom = INPUT_METHOD_WINDOW_TOP;
-            mFrames.mCurrent.bottom = INPUT_METHOD_WINDOW_TOP;
-            mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
-            int bottomInset = mFrames.mDisplayHeight - INPUT_METHOD_WINDOW_TOP;
-            assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
-            assertInsetByTopBottom(mWindow.getContentFrameLw(), 0, 0);
-            assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, bottomInset);
-        }
-    }
-
-    // TODO(b/118118435): remove after removing PolicyControl
-    @FlakyTest(bugId = 129711077)
-    @Test
-    public void layoutWindowLw_withImmersive_SoftInputAdjustNothing() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
-
-        synchronized (mWm.mGlobalLock) {
-            mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_NOTHING;
-            mWindow.mAttrs.flags = 0;
-            mWindow.mAttrs.systemUiVisibility =
-                    SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
-                            | SYSTEM_UI_FLAG_FULLSCREEN | SYSTEM_UI_FLAG_HIDE_NAVIGATION;
-
-            addWindow(mWindow);
-
-            mWindow.getDisplayContent().mInputMethodTarget = mWindow;
-            mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
-            mFrames.mContent.bottom = mFrames.mVoiceContent.bottom = INPUT_METHOD_WINDOW_TOP;
-            mFrames.mCurrent.bottom = INPUT_METHOD_WINDOW_TOP;
-            mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
-            assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
-            assertInsetByTopBottom(mWindow.getContentFrameLw(), 0, 0);
-            assertInsetByTopBottom(mWindow.getVisibleFrameLw(), 0, 0);
-        }
-    }
-
-    // TODO(b/118118435): remove after removing PolicyControl
-    @FlakyTest(bugId = 129711077)
-    @Test
-    public void layoutWindowLw_withForceImmersive_fullscreen() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
-
-        synchronized (mWm.mGlobalLock) {
-            mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
-            mWindow.mAttrs.flags = 0;
-            mWindow.mAttrs.systemUiVisibility = 0;
-            PolicyControl.setFilters(PolicyControl.NAME_IMMERSIVE_FULL + "=*");
-
-            addWindow(mWindow);
-
-            mWindow.getDisplayContent().mInputMethodTarget = mWindow;
-            mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
-            mFrames.mContent.bottom = mFrames.mVoiceContent.bottom = INPUT_METHOD_WINDOW_TOP;
-            mFrames.mCurrent.bottom = INPUT_METHOD_WINDOW_TOP;
-            mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
-            int bottomInset = mFrames.mDisplayHeight - INPUT_METHOD_WINDOW_TOP;
-            assertInsetByTopBottom(mWindow.getParentFrame(), 0, 0);
-            assertInsetByTopBottom(mWindow.getContentFrameLw(), 0, 0);
-            assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, bottomInset);
-        }
-    }
-
-    // TODO(b/118118435): remove after removing PolicyControl
-    @FlakyTest(bugId = 129711077)
-    @Test
-    public void layoutWindowLw_withForceImmersive_nonFullscreen() {
-        assumeTrue(ViewRootImpl.sNewInsetsMode != ViewRootImpl.NEW_INSETS_MODE_FULL);
-
-        synchronized (mWm.mGlobalLock) {
-            mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
-            mWindow.mAttrs.flags = 0;
-            mWindow.mAttrs.systemUiVisibility = 0;
-            mWindow.mAttrs.width = DISPLAY_WIDTH / 2;
-            mWindow.mAttrs.height = DISPLAY_HEIGHT / 2;
-            PolicyControl.setFilters(PolicyControl.NAME_IMMERSIVE_FULL + "=*");
-
-            addWindow(mWindow);
-
-            mWindow.getDisplayContent().mInputMethodTarget = mWindow;
-            mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
-            mFrames.mContent.bottom = mFrames.mVoiceContent.bottom = INPUT_METHOD_WINDOW_TOP;
-            mFrames.mCurrent.bottom = INPUT_METHOD_WINDOW_TOP;
-            mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames);
-
-            int bottomInset = mFrames.mDisplayHeight - INPUT_METHOD_WINDOW_TOP;
-            assertInsetByTopBottom(mWindow.getParentFrame(), STATUS_BAR_HEIGHT, bottomInset);
-            assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, bottomInset);
-            assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, bottomInset);
-        }
-    }
-
     @Test
     public void layoutHint_appWindow() {
         mWindow.mAttrs.flags =
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
index 552c476..79ba175 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimatorTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 
 import android.platform.test.annotations.Presubmit;
+import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Builder;
 import android.view.SurfaceControl.Transaction;
@@ -52,6 +53,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.PrintWriter;
+
 /**
  * Test class for {@link SurfaceAnimatorTest}.
  *
@@ -267,6 +270,27 @@
         assertFalse(mDeferFinishAnimatable.mFinishedCallbackCalled);
     }
 
+    @Test
+    public void testDeferFinishFromAdapter() {
+
+        DeferredFinishAdapter deferredFinishAdapter = new DeferredFinishAdapter();
+        // Start animation
+        mAnimatable.mSurfaceAnimator.startAnimation(mTransaction, deferredFinishAdapter,
+                true /* hidden */,
+                ANIMATION_TYPE_APP_TRANSITION);
+        assertAnimating(mAnimatable);
+        deferredFinishAdapter.mFinishCallback.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION,
+                deferredFinishAdapter);
+
+        assertAnimating(mAnimatable);
+        assertFalse(mAnimatable.mFinishedCallbackCalled);
+        // Now end defer finishing.
+        deferredFinishAdapter.mEndDeferFinishCallback.run();
+        assertNotAnimating(mAnimatable);
+        assertTrue(mAnimatable.mFinishedCallbackCalled);
+        verify(mTransaction).remove(eq(deferredFinishAdapter.mAnimationLeash));
+    }
+
     private OnAnimationFinishedCallback startDeferFinishAnimatable(AnimationAdapter anim) {
         mDeferFinishAnimatable.mSurfaceAnimator.startAnimation(mTransaction, anim,
                 true /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
@@ -389,4 +413,51 @@
             return true;
         }
     }
+
+    private static class DeferredFinishAdapter implements AnimationAdapter {
+
+        private Runnable mEndDeferFinishCallback;
+        private OnAnimationFinishedCallback mFinishCallback;
+        private SurfaceControl mAnimationLeash;
+
+        @Override
+        public boolean getShowWallpaper() {
+            return true;
+        }
+
+        @Override
+        public void startAnimation(SurfaceControl animationLeash, Transaction t, int type,
+                OnAnimationFinishedCallback finishCallback) {
+            mFinishCallback = finishCallback;
+            mAnimationLeash = animationLeash;
+        }
+
+        @Override
+        public void onAnimationCancelled(SurfaceControl animationLeash) {
+        }
+
+        @Override
+        public long getDurationHint() {
+            return 100;
+        }
+
+        @Override
+        public long getStatusBarTransitionsStartTime() {
+            return 100;
+        }
+
+        @Override
+        public void dump(PrintWriter pw, String prefix) {
+        }
+
+        @Override
+        public void dumpDebug(ProtoOutputStream proto) {
+        }
+
+        @Override
+        public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
+            mEndDeferFinishCallback = endDeferFinishCallback;
+            return true;
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 65fb2c0..0346329 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -22,7 +22,6 @@
 import static android.hardware.camera2.params.OutputConfiguration.ROTATION_90;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.Surface.ROTATION_0;
-import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
@@ -75,17 +74,13 @@
 import android.view.DisplayCutout;
 import android.view.InsetsSource;
 import android.view.SurfaceControl;
-import android.view.ViewRootImpl;
 import android.view.WindowManager;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.server.wm.utils.WmDisplayCutout;
 
-import org.junit.AfterClass;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -103,21 +98,6 @@
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class WindowStateTests extends WindowTestsBase {
-    private static int sPreviousNewInsetsMode;
-
-    @BeforeClass
-    public static void setUpOnce() {
-        // TODO: Make use of SettingsSession when it becomes feasible for this.
-        sPreviousNewInsetsMode = ViewRootImpl.sNewInsetsMode;
-        // To let the insets provider control the insets visibility, the insets mode has to be
-        // NEW_INSETS_MODE_FULL.
-        ViewRootImpl.sNewInsetsMode = NEW_INSETS_MODE_FULL;
-    }
-
-    @AfterClass
-    public static void tearDownOnce() {
-        ViewRootImpl.sNewInsetsMode = sPreviousNewInsetsMode;
-    }
 
     @Before
     public void setUp() {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e85dfbe..4246aef 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -10415,7 +10415,7 @@
      * <p>To recognize a carrier (including MVNO) as a first-class identity, Android assigns each
      * carrier with a canonical integer a.k.a. carrier id. The carrier ID is an Android
      * platform-wide identifier for a carrier. AOSP maintains carrier ID assignments in
-     * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a>
+     * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb">here</a>
      *
      * <p>Apps which have carrier-specific configurations or business logic can use the carrier id
      * as an Android platform-wide identifier for carriers.
@@ -10477,7 +10477,7 @@
      *
      * <p>For carriers without fine-grained specific carrier ids, return {@link #getSimCarrierId()}
      * <p>Specific carrier ids are defined in the same way as carrier id
-     * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a>
+     * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb">here</a>
      * except each with a "parent" id linking to its top-level carrier id.
      *
      * @return Returns fine-grained carrier id of the current subscription.
@@ -10526,7 +10526,7 @@
      * This is used for fallback when configurations/logic for exact carrier id
      * {@link #getSimCarrierId()} are not found.
      *
-     * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a>
+     * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb">here</a>
      * can be updated out-of-band, its possible a MVNO (Mobile Virtual Network Operator) carrier
      * was not fully recognized and assigned to its MNO (Mobile Network Operator) carrier id
      * by default. After carrier id table update, a new carrier id was assigned. If apps don't
@@ -10553,7 +10553,7 @@
       * used for fallback when configurations/logic for exact carrier id {@link #getSimCarrierId()}
       * are not found.
       *
-      * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">here</a>
+      * Android carrier id table <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/latest_carrier_id/carrier_list.textpb">here</a>
       * can be updated out-of-band, its possible a MVNO (Mobile Virtual Network Operator) carrier
       * was not fully recognized and assigned to its MNO (Mobile Network Operator) carrier id
       * by default. After carrier id table update, a new carrier id was assigned. If apps don't
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
index eec3cdbe..8c2de40 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/TestNetworkStackService.kt
@@ -52,7 +52,7 @@
         doReturn(mock(IBinder::class.java)).`when`(it).getSystemService(Context.NETD_SERVICE)
     }
 
-    private class TestPermissionChecker : NetworkStackConnector.PermissionChecker() {
+    private class TestPermissionChecker : NetworkStackService.PermissionChecker() {
         override fun enforceNetworkStackCallingPermission() = Unit
     }
 
@@ -62,8 +62,8 @@
         override fun sendNetworkConditionsBroadcast(context: Context, broadcast: Intent) = Unit
     }
 
-    private inner class TestNetworkStackConnector(context: Context) :
-            NetworkStackConnector(context, TestPermissionChecker()) {
+    private inner class TestNetworkStackConnector(context: Context) : NetworkStackConnector(
+            context, TestPermissionChecker(), NetworkStackService.Dependencies()) {
 
         private val network = Network(TEST_NETID)
         private val privateDnsBypassNetwork = TestNetwork(TEST_NETID)
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index d299cdc..94771ac 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -131,10 +131,10 @@
     public @interface WifiBand {}
 
     /**
-     * Max band value
+     * All bands
      * @hide
      */
-    public static final int WIFI_BAND_MAX = 0x10;
+    public static final int WIFI_BAND_ALL = (1 << WIFI_BAND_COUNT) - 1;
 
     /** Minimum supported scanning period */
     public static final int MIN_SCAN_PERIOD_MS = 1000;
@@ -168,6 +168,22 @@
     }
 
     /**
+     * Test if scan is a full scan. i.e. scanning all available bands.
+     * For backward compatibility, since some apps don't include 6GHz in their requests yet,
+     * lacking 6GHz band does not cause the result to be false.
+     *
+     * @param bandScanned bands that are fully scanned
+     * @param excludeDfs when true, DFS band is excluded from the check
+     * @return true if all bands are scanned, false otherwise
+     *
+     * @hide
+     */
+    public static boolean isFullBandScan(@WifiBand int bandScanned, boolean excludeDfs) {
+        return (bandScanned | WIFI_BAND_6_GHZ | (excludeDfs ? WIFI_BAND_5_GHZ_DFS_ONLY : 0))
+                == WIFI_BAND_ALL;
+    }
+
+    /**
      * Returns a list of all the possible channels for the given band(s).
      *
      * @param band one of the WifiScanner#WIFI_BAND_* constants, e.g. {@link #WIFI_BAND_24_GHZ}
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index 4881200..b68616f 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -613,4 +613,22 @@
         verify(mExecutor, never()).execute(any());
         verify(mScanListener, never()).onResults(mScanData);
     }
+
+    /**
+     * Tests isFullBandScan() method with and without DFS check
+     */
+    @Test
+    public void testIsFullBandScan() throws Exception {
+        assertFalse(WifiScanner.isFullBandScan(WifiScanner.WIFI_BAND_24_GHZ, true));
+        assertFalse(WifiScanner.isFullBandScan(WifiScanner.WIFI_BAND_5_GHZ, true));
+        assertFalse(WifiScanner.isFullBandScan(WifiScanner.WIFI_BAND_6_GHZ, true));
+        assertFalse(WifiScanner.isFullBandScan(
+                WifiScanner.WIFI_BAND_6_GHZ | WifiScanner.WIFI_BAND_5_GHZ, true));
+        assertTrue(WifiScanner.isFullBandScan(
+                WifiScanner.WIFI_BAND_24_GHZ | WifiScanner.WIFI_BAND_5_GHZ, true));
+        assertFalse(WifiScanner.isFullBandScan(
+                WifiScanner.WIFI_BAND_24_GHZ | WifiScanner.WIFI_BAND_5_GHZ, false));
+        assertTrue(WifiScanner.isFullBandScan(WifiScanner.WIFI_BAND_BOTH_WITH_DFS, true));
+        assertTrue(WifiScanner.isFullBandScan(WifiScanner.WIFI_BAND_BOTH_WITH_DFS, false));
+    }
 }