Merge "Return Job in collectLatestWithLifecycle" into main
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index eef3368..606e038 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -93,8 +93,7 @@
         if (bg != null) {
             bg.setCallback(this);
             if (mActionBarView != null) {
-                mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
-                        mActionBarView.getRight(), mActionBarView.getBottom());
+                bg.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
             }
         }
         setWillNotDraw(mIsSplit ? mSplitBackground == null :
@@ -293,6 +292,7 @@
         if (mActionBarView == null) return;
 
         if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
+            final int verticalPadding = getPaddingTop() + getPaddingBottom();
             int nonTabMaxHeight = 0;
             final int childCount = getChildCount();
             for (int i = 0; i < childCount; i++) {
@@ -307,7 +307,9 @@
             final int maxHeight = mode == MeasureSpec.AT_MOST ?
                     MeasureSpec.getSize(heightMeasureSpec) : Integer.MAX_VALUE;
             setMeasuredDimension(getMeasuredWidth(),
-                    Math.min(nonTabMaxHeight + getMeasuredHeightWithMargins(mTabContainer),
+                    Math.min(
+                            verticalPadding + nonTabMaxHeight
+                                    + getMeasuredHeightWithMargins(mTabContainer),
                             maxHeight));
         }
     }
@@ -335,13 +337,9 @@
             }
         } else {
             if (mBackground != null) {
-                if (mActionBarView.getVisibility() == View.VISIBLE) {
-                    mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(),
-                            mActionBarView.getRight(), mActionBarView.getBottom());
-                } else if (mActionContextView != null &&
-                        mActionContextView.getVisibility() == View.VISIBLE) {
-                    mBackground.setBounds(mActionContextView.getLeft(), mActionContextView.getTop(),
-                            mActionContextView.getRight(), mActionContextView.getBottom());
+                if ((mActionBarView.getVisibility() == View.VISIBLE) || (mActionContextView != null
+                        && mActionContextView.getVisibility() == View.VISIBLE)) {
+                    mBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight());
                 } else {
                     mBackground.setBounds(0, 0, 0, 0);
                 }
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index 0992db9..707f109 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -24,6 +24,7 @@
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -77,10 +78,13 @@
     private final Rect mBaseContentInsets = new Rect();
     private final Rect mLastBaseContentInsets = new Rect();
     private final Rect mContentInsets = new Rect();
+    private final Rect mSystemInsets = new Rect();
     private WindowInsets mBaseInnerInsets = WindowInsets.CONSUMED;
     private WindowInsets mLastBaseInnerInsets = WindowInsets.CONSUMED;
     private WindowInsets mInnerInsets = WindowInsets.CONSUMED;
     private WindowInsets mLastInnerInsets = WindowInsets.CONSUMED;
+    private boolean mDecorFitsSystemWindows = true;
+    private boolean mActionBarExtendsIntoSystemInsets = false;
 
     private ActionBarVisibilityCallback mActionBarVisibilityCallback;
 
@@ -268,7 +272,8 @@
             // We want the bar to be visible if it is not being hidden,
             // or the app has not turned on a stable UI mode (meaning they
             // are performing explicit layout around the action bar).
-            mActionBarVisibilityCallback.enableContentAnimations(!stable);
+            mActionBarVisibilityCallback.enableContentAnimations(
+                    !stable && !mActionBarExtendsIntoSystemInsets);
             if (barVisible || !stable) mActionBarVisibilityCallback.showForSystem();
             else mActionBarVisibilityCallback.hideForSystem();
         }
@@ -288,25 +293,56 @@
         }
     }
 
-    private boolean applyInsets(View view, Rect insets, boolean left, boolean top,
-            boolean bottom, boolean right) {
+    private boolean applyInsets(View view, Rect insets, boolean toPadding,
+            boolean left, boolean top, boolean bottom, boolean right) {
+        boolean changed;
+        if (toPadding) {
+            changed = setMargin(view, left, top, bottom, right, 0, 0, 0, 0);
+            changed |= setPadding(view, left, top, bottom, right,
+                    insets.left, insets.top, insets.right, insets.bottom);
+        } else {
+            changed = setPadding(view, left, top, bottom, right, 0, 0, 0, 0);
+            changed |= setMargin(view, left, top, bottom, right,
+                    insets.left, insets.top, insets.right, insets.bottom);
+        }
+        return changed;
+    }
+
+    private boolean setPadding(View view, boolean left, boolean top, boolean bottom, boolean right,
+            int l, int t, int r, int b) {
+        if ((left && view.getPaddingLeft() != l)
+                || (top && view.getPaddingTop() != t)
+                || (right && view.getPaddingRight() != r)
+                || (bottom && view.getPaddingBottom() != b)) {
+            view.setPadding(
+                    left ? l : view.getPaddingLeft(),
+                    top ? t : view.getPaddingTop(),
+                    right ? r : view.getPaddingRight(),
+                    bottom ? b : view.getPaddingBottom());
+            return true;
+        }
+        return false;
+    }
+
+    private boolean setMargin(View view, boolean left, boolean top, boolean bottom, boolean right,
+            int l, int t, int r, int b) {
+        LayoutParams lp = (LayoutParams) view.getLayoutParams();
         boolean changed = false;
-        LayoutParams lp = (LayoutParams)view.getLayoutParams();
-        if (left && lp.leftMargin != insets.left) {
+        if (left && lp.leftMargin != l) {
             changed = true;
-            lp.leftMargin = insets.left;
+            lp.leftMargin = l;
         }
-        if (top && lp.topMargin != insets.top) {
+        if (top && lp.topMargin != t) {
             changed = true;
-            lp.topMargin = insets.top;
+            lp.topMargin = t;
         }
-        if (right && lp.rightMargin != insets.right) {
+        if (right && lp.rightMargin != r) {
             changed = true;
-            lp.rightMargin = insets.right;
+            lp.rightMargin = r;
         }
-        if (bottom && lp.bottomMargin != insets.bottom) {
+        if (bottom && lp.bottomMargin != b) {
             changed = true;
-            lp.bottomMargin = insets.bottom;
+            lp.bottomMargin = b;
         }
         return changed;
     }
@@ -316,12 +352,28 @@
         pullChildren();
 
         final int vis = getWindowSystemUiVisibility();
-        final Rect systemInsets = insets.getSystemWindowInsetsAsRect();
+        final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
+        final boolean layoutIntoSystemInsets = (vis & SYSTEM_UI_LAYOUT_FLAGS) != 0;
+        mDecorFitsSystemWindows = hasContentOnApplyWindowInsetsListener();
+
+        // Only extend action bar into system insets area if the app doesn't fit system insets.
+        mActionBarExtendsIntoSystemInsets =
+                !mDecorFitsSystemWindows || (stable && layoutIntoSystemInsets);
+
+        if (mActionBarVisibilityCallback != null) {
+            mActionBarVisibilityCallback.enableContentAnimations(
+                    !stable && !mActionBarExtendsIntoSystemInsets);
+        }
+
+        final Insets sysInsets = insets.getSystemWindowInsets();
+        mSystemInsets.set(sysInsets.left, sysInsets.top, sysInsets.right, sysInsets.bottom);
 
         // The top and bottom action bars are always within the content area.
-        boolean changed = applyInsets(mActionBarTop, systemInsets, true, true, false, true);
+        boolean changed = applyInsets(mActionBarTop, mSystemInsets,
+                mActionBarExtendsIntoSystemInsets, true, true, false, true);
         if (mActionBarBottom != null) {
-            changed |= applyInsets(mActionBarBottom, systemInsets, true, false, true, true);
+            changed |= applyInsets(mActionBarBottom, mSystemInsets,
+                    mActionBarExtendsIntoSystemInsets, true, false, true, true);
         }
 
         // Cannot use the result of computeSystemWindowInsets, because that consumes the
@@ -406,6 +458,9 @@
             // This is the standard space needed for the action bar.  For stable measurement,
             // we can't depend on the size currently reported by it -- this must remain constant.
             topInset = mActionBarHeight;
+            if (mActionBarExtendsIntoSystemInsets) {
+                topInset += mSystemInsets.top;
+            }
             if (mHasNonEmbeddedTabs) {
                 final View tabs = mActionBarTop.getTabContainer();
                 if (tabs != null) {
@@ -424,6 +479,9 @@
             if (mActionBarBottom != null) {
                 if (stable) {
                     bottomInset = mActionBarHeight;
+                    if (mActionBarExtendsIntoSystemInsets) {
+                        bottomInset += mSystemInsets.bottom;
+                    }
                 } else {
                     bottomInset = mActionBarBottom.getMeasuredHeight();
                 }
@@ -436,21 +494,35 @@
         // overlay.
         mContentInsets.set(mBaseContentInsets);
         mInnerInsets = mBaseInnerInsets;
-        if (!mOverlayMode && !stable && hasContentOnApplyWindowInsetsListener()) {
-            mContentInsets.top += topInset;
-            mContentInsets.bottom += bottomInset;
+        if (!mOverlayMode && !stable && mDecorFitsSystemWindows) {
+            if (mActionBarExtendsIntoSystemInsets) {
+                mContentInsets.top = Math.max(mContentInsets.top, topInset);
+                mContentInsets.bottom = Math.max(mContentInsets.bottom, bottomInset);
+            } else {
+                mContentInsets.top += topInset;
+                mContentInsets.bottom += bottomInset;
+            }
             // Content view has been shrunk, shrink all insets to match.
             mInnerInsets = mInnerInsets.inset(0 /* left */, topInset, 0 /* right */, bottomInset);
         } else {
             // Add ActionBar to system window inset, but leave other insets untouched.
-            mInnerInsets = mInnerInsets.replaceSystemWindowInsets(
-                    mInnerInsets.getSystemWindowInsetLeft(),
-                    mInnerInsets.getSystemWindowInsetTop() + topInset,
-                    mInnerInsets.getSystemWindowInsetRight(),
-                    mInnerInsets.getSystemWindowInsetBottom() + bottomInset
-            );
+            if (mActionBarExtendsIntoSystemInsets) {
+                mInnerInsets = mInnerInsets.replaceSystemWindowInsets(
+                        mInnerInsets.getSystemWindowInsetLeft(),
+                        Math.max(mInnerInsets.getSystemWindowInsetTop(), topInset),
+                        mInnerInsets.getSystemWindowInsetRight(),
+                        Math.max(mInnerInsets.getSystemWindowInsetBottom(), bottomInset)
+                );
+            } else {
+                mInnerInsets = mInnerInsets.replaceSystemWindowInsets(
+                        mInnerInsets.getSystemWindowInsetLeft(),
+                        mInnerInsets.getSystemWindowInsetTop() + topInset,
+                        mInnerInsets.getSystemWindowInsetRight(),
+                        mInnerInsets.getSystemWindowInsetBottom() + bottomInset
+                );
+            }
         }
-        applyInsets(mContent, mContentInsets, true, true, true, true);
+        applyInsets(mContent, mContentInsets, false /* toPadding */, true, true, true, true);
 
         if (!mLastInnerInsets.equals(mInnerInsets)) {
             // If the inner insets have changed, we need to dispatch this down to
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index 17129d8..fec2898 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -245,7 +245,6 @@
 }
 
 void nativeWritePackets(JNIEnv* env, jclass clazz, jlong ds_ptr, jobjectArray packets) {
-    ALOG(LOG_DEBUG, LOG_TAG, "nativeWritePackets(%p)", (void*)ds_ptr);
     sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ds_ptr);
     datasource->WritePackets(env, packets);
 }
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index 1dbb775..2b8adcb 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -20,6 +20,7 @@
 import static android.view.View.MeasureSpec.EXACTLY;
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.Matchers.is;
@@ -69,6 +70,7 @@
 
     private ViewGroup mContent;
     private ViewGroup mActionBarTop;
+    private ViewGroup mActionBarView;
     private Toolbar mToolbar;
     private FakeOnApplyWindowListener mContentInsetsListener;
 
@@ -86,15 +88,22 @@
         mContentInsetsListener = new FakeOnApplyWindowListener();
         mContent.setOnApplyWindowInsetsListener(mContentInsetsListener);
 
-        mActionBarTop = new ActionBarContainer(mContext);
-        mActionBarTop.setId(com.android.internal.R.id.action_bar_container);
-        mActionBarTop.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, 20));
-        mLayout.addView(mActionBarTop);
-        mLayout.setActionBarHeight(20);
+        // mActionBarView and mToolbar are supposed to be the same view. Here makes mToolbar a child
+        // of mActionBarView is to control the height of mActionBarView. In this way, the child
+        // views of mToolbar won't affect the measurement of mActionBarView or mActionBarTop.
+        mActionBarView = new FrameLayout(mContext);
+        mActionBarView.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, 20));
 
         mToolbar = new Toolbar(mContext);
         mToolbar.setId(com.android.internal.R.id.action_bar);
-        mActionBarTop.addView(mToolbar);
+        mActionBarView.addView(mToolbar);
+
+        mActionBarTop = new ActionBarContainer(mContext);
+        mActionBarTop.setId(com.android.internal.R.id.action_bar_container);
+        mActionBarTop.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+        mActionBarTop.addView(mActionBarView);
+        mLayout.addView(mActionBarTop);
+        mLayout.setActionBarHeight(20);
     }
 
     @Test
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index eade86e..d888fa9 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -658,27 +658,28 @@
     }
 
     /**
-     * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
-     * covered by the task bounds. Otherwise, returns {@code bounds}.
+     * Returns the expanded bounds if the {@code relBounds} violate minimum dimension or are not
+     * fully covered by the task bounds. Otherwise, returns {@code relBounds}.
      */
     @NonNull
-    static Rect sanitizeBounds(@NonNull Rect bounds, @Nullable Size minDimension,
+    static Rect sanitizeBounds(@NonNull Rect relBounds, @Nullable Size minDimension,
                         @NonNull TaskFragmentContainer container) {
-        if (bounds.isEmpty()) {
+        if (relBounds.isEmpty()) {
             // Don't need to check if the bounds follows the task bounds.
-            return bounds;
+            return relBounds;
         }
-        if (boundsSmallerThanMinDimensions(bounds, minDimension)) {
+        if (boundsSmallerThanMinDimensions(relBounds, minDimension)) {
             // Expand the bounds if the bounds are smaller than minimum dimensions.
             return new Rect();
         }
         final TaskContainer taskContainer = container.getTaskContainer();
-        final Rect taskBounds = taskContainer.getBounds();
-        if (!taskBounds.contains(bounds)) {
+        final Rect relTaskBounds = new Rect(taskContainer.getBounds());
+        relTaskBounds.offsetTo(0, 0);
+        if (!relTaskBounds.contains(relBounds)) {
             // Expand the bounds if the bounds exceed the task bounds.
             return new Rect();
         }
-        return bounds;
+        return relBounds;
     }
 
     @Override
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 0972d40..7a0b9a0 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -409,6 +409,22 @@
     }
 
     @Test
+    public void testSanitizeBounds_taskInSplitScreen() {
+        final TaskFragmentContainer overlayContainer =
+                createTestOverlayContainer(TASK_ID, "test1");
+        TaskContainer taskContainer = overlayContainer.getTaskContainer();
+        spyOn(taskContainer);
+        doReturn(new Rect(TASK_BOUNDS.left + TASK_BOUNDS.width() / 2, TASK_BOUNDS.top,
+                TASK_BOUNDS.right, TASK_BOUNDS.bottom)).when(taskContainer).getBounds();
+        final Rect taskBounds = taskContainer.getBounds();
+        final Rect bounds = new Rect(taskBounds.width() / 2, 0, taskBounds.width(),
+                taskBounds.height());
+
+        assertThat(sanitizeBounds(bounds, null, overlayContainer)
+                .isEmpty()).isFalse();
+    }
+
+    @Test
     public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_createOverlay() {
         final Rect bounds = new Rect(0, 0, 100, 100);
         mSplitController.setActivityStackAttributesCalculator(params ->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 14c9999..83a8d4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -962,7 +962,7 @@
         }
         val wct = WindowContainerTransaction()
         if (isDesktopDensityOverrideSet()) {
-            wct.setDensityDpi(task.token, DESKTOP_DENSITY_OVERRIDE)
+            // TODO(344599474) reintroduce density changes behind a disabled flag
         }
         // Desktop Mode is showing and we're launching a new Task - we might need to minimize
         // a Task.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index ae05bf5..b526838 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1113,6 +1113,8 @@
 
   @Test
   fun handleRequest_freeformTask_alreadyInDesktop_noOverrideDensity_noConfigDensityChange() {
+    // TODO(344599474) enable the test once the density change is behind a flag
+    assumeTrue(false)
     assumeTrue(ENABLE_SHELL_TRANSITIONS)
     whenever(DesktopModeStatus.isDesktopDensityOverrideSet()).thenReturn(false)
 
@@ -1127,6 +1129,8 @@
 
   @Test
   fun handleRequest_freeformTask_alreadyInDesktop_overrideDensity_hasConfigDensityChange() {
+    // TODO(344599474) enable the test once the density change is behind a flag
+    assumeTrue(false)
     assumeTrue(ENABLE_SHELL_TRANSITIONS)
     whenever(DesktopModeStatus.isDesktopDensityOverrideSet()).thenReturn(true)
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
index 42389f0..4438763 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
@@ -23,16 +23,23 @@
 import android.net.Uri;
 import android.provider.Settings;
 
+import com.android.systemui.dagger.qualifiers.Background;
+
+import kotlinx.coroutines.CoroutineDispatcher;
+
 import javax.inject.Inject;
 
 // use UserHandle.USER_SYSTEM everywhere
 @SuppressLint("StaticSettingsProvider")
 class GlobalSettingsImpl implements GlobalSettings {
     private final ContentResolver mContentResolver;
+    private final CoroutineDispatcher mBgDispatcher;
 
     @Inject
-    GlobalSettingsImpl(ContentResolver contentResolver) {
+    GlobalSettingsImpl(ContentResolver contentResolver,
+            @Background CoroutineDispatcher bgDispatcher) {
         mContentResolver = contentResolver;
+        mBgDispatcher = bgDispatcher;
     }
 
     @Override
@@ -46,6 +53,11 @@
     }
 
     @Override
+    public CoroutineDispatcher getBackgroundDispatcher() {
+        return mBgDispatcher;
+    }
+
+    @Override
     public String getString(String name) {
         return Settings.Global.getString(mContentResolver, name);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
index 6532ce8..38ad5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
@@ -22,18 +22,24 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.settings.UserTracker;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+
 import javax.inject.Inject;
 
 class SecureSettingsImpl implements SecureSettings {
     private final ContentResolver mContentResolver;
     private final UserTracker mUserTracker;
+    private final CoroutineDispatcher mBgDispatcher;
 
     @Inject
-    SecureSettingsImpl(ContentResolver contentResolver, UserTracker userTracker) {
+    SecureSettingsImpl(ContentResolver contentResolver, UserTracker userTracker,
+            @Background CoroutineDispatcher bgDispatcher) {
         mContentResolver = contentResolver;
         mUserTracker = userTracker;
+        mBgDispatcher = bgDispatcher;
     }
 
     @Override
@@ -52,6 +58,11 @@
     }
 
     @Override
+    public CoroutineDispatcher getBackgroundDispatcher() {
+        return mBgDispatcher;
+    }
+
+    @Override
     public String getStringForUser(String name, int userHandle) {
         return Settings.Secure.getStringForUser(mContentResolver, name,
                 getRealUserHandle(userHandle));
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
index ed52233..55171ad 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -19,6 +19,10 @@
 import android.database.ContentObserver
 import android.net.Uri
 import android.provider.Settings.SettingNotFoundException
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /**
  * Used to interact with mainly with Settings.Global, but can also be used for Settings.System and
@@ -37,6 +41,13 @@
 interface SettingsProxy {
     /** Returns the [ContentResolver] this instance was constructed with. */
     fun getContentResolver(): ContentResolver
+
+    /**
+     * Returns the background [CoroutineDispatcher] that the async APIs will use for a specific
+     * implementation.
+     */
+    fun getBackgroundDispatcher(): CoroutineDispatcher
+
     /**
      * Construct the content URI for a particular name/value pair, useful for monitoring changes
      * with a ContentObserver.
@@ -45,6 +56,7 @@
      * @return the corresponding content URI, or null if not present
      */
     fun getUriFor(name: String): Uri
+
     /**
      * Convenience wrapper around [ContentResolver.registerContentObserver].'
      *
@@ -53,9 +65,55 @@
     fun registerContentObserverSync(name: String, settingsObserver: ContentObserver) {
         registerContentObserverSync(getUriFor(name), settingsObserver)
     }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * suspend API corresponding to [registerContentObserver] to ensure that [ContentObserver]
+     * registration happens on a worker thread. Caller may wrap the API in an async block if they
+     * wish to synchronize execution.
+     */
+    suspend fun registerContentObserver(name: String, settingsObserver: ContentObserver) =
+        withContext(getBackgroundDispatcher()) {
+            registerContentObserverSync(getUriFor(name), settingsObserver)
+        }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * API corresponding to [registerContentObserver] for Java usage.
+     */
+    fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) =
+        CoroutineScope(getBackgroundDispatcher()).launch {
+            registerContentObserverSync(getUriFor(name), settingsObserver)
+        }
+
     /** Convenience wrapper around [ContentResolver.registerContentObserver].' */
     fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) =
         registerContentObserverSync(uri, false, settingsObserver)
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * suspend API corresponding to [registerContentObserver] to ensure that [ContentObserver]
+     * registration happens on a worker thread. Caller may wrap the API in an async block if they
+     * wish to synchronize execution.
+     */
+    suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) =
+        withContext(getBackgroundDispatcher()) {
+            registerContentObserverSync(uri, settingsObserver)
+        }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * API corresponding to [registerContentObserver] for Java usage.
+     */
+    fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) =
+        CoroutineScope(getBackgroundDispatcher()).launch {
+            registerContentObserverSync(uri, settingsObserver)
+        }
+
     /**
      * Convenience wrapper around [ContentResolver.registerContentObserver].'
      *
@@ -66,15 +124,102 @@
         notifyForDescendants: Boolean,
         settingsObserver: ContentObserver
     ) = registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * suspend API corresponding to [registerContentObserver] to ensure that [ContentObserver]
+     * registration happens on a worker thread. Caller may wrap the API in an async block if they
+     * wish to synchronize execution.
+     */
+    suspend fun registerContentObserver(
+        name: String,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver
+    ) =
+        withContext(getBackgroundDispatcher()) {
+            registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
+        }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * API corresponding to [registerContentObserver] for Java usage.
+     */
+    fun registerContentObserverAsync(
+        name: String,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver
+    ) =
+        CoroutineScope(getBackgroundDispatcher()).launch {
+            registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
+        }
+
     /** Convenience wrapper around [ContentResolver.registerContentObserver].' */
     fun registerContentObserverSync(
         uri: Uri,
         notifyForDescendants: Boolean,
         settingsObserver: ContentObserver
-    ) = getContentResolver().registerContentObserver(uri, notifyForDescendants, settingsObserver)
+    ) {
+        getContentResolver().registerContentObserver(uri, notifyForDescendants, settingsObserver)
+    }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * suspend API corresponding to [registerContentObserver] to ensure that [ContentObserver]
+     * registration happens on a worker thread. Caller may wrap the API in an async block if they
+     * wish to synchronize execution.
+     */
+    suspend fun registerContentObserver(
+        uri: Uri,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver
+    ) =
+        withContext(getBackgroundDispatcher()) {
+            registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
+        }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * API corresponding to [registerContentObserver] for Java usage.
+     */
+    fun registerContentObserverAsync(
+        uri: Uri,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver
+    ) =
+        CoroutineScope(getBackgroundDispatcher()).launch {
+            getContentResolver()
+                .registerContentObserver(uri, notifyForDescendants, settingsObserver)
+        }
+
     /** See [ContentResolver.unregisterContentObserver]. */
     fun unregisterContentObserverSync(settingsObserver: ContentObserver) =
         getContentResolver().unregisterContentObserver(settingsObserver)
+
+    /**
+     * Convenience wrapper around [ContentResolver.unregisterContentObserver].'
+     *
+     * API corresponding to [unregisterContentObserver] for Java usage to ensure that
+     * [ContentObserver] un-registration happens on a worker thread. Caller may wrap the API in an
+     * async block if they wish to synchronize execution.
+     */
+    suspend fun unregisterContentObserver(settingsObserver: ContentObserver) =
+        withContext(getBackgroundDispatcher()) { unregisterContentObserverSync(settingsObserver) }
+
+    /**
+     * Convenience wrapper around [ContentResolver.unregisterContentObserver].'
+     *
+     * API corresponding to [unregisterContentObserver] for Java usage to ensure that
+     * [ContentObserver] registration happens on a worker thread.
+     */
+    fun unregisterContentObserverAsync(settingsObserver: ContentObserver) =
+        CoroutineScope(getBackgroundDispatcher()).launch {
+            unregisterContentObserver(settingsObserver)
+        }
+
     /**
      * Look up a name in the database.
      *
@@ -82,6 +227,7 @@
      * @return the corresponding value, or null if not present
      */
     fun getString(name: String): String
+
     /**
      * Store a name/value pair into the database.
      *
@@ -90,6 +236,7 @@
      * @return true if the value was set, false on database errors
      */
     fun putString(name: String, value: String): Boolean
+
     /**
      * Store a name/value pair into the database.
      *
@@ -120,6 +267,7 @@
      * @see .resetToDefaults
      */
     fun putString(name: String, value: String, tag: String, makeDefault: Boolean): Boolean
+
     /**
      * Convenience function for retrieving a single secure settings value as an integer. Note that
      * internally setting values are always stored as strings; this function converts the string to
@@ -138,6 +286,7 @@
             def
         }
     }
+
     /**
      * Convenience function for retrieving a single secure settings value as an integer. Note that
      * internally setting values are always stored as strings; this function converts the string to
@@ -160,6 +309,7 @@
             throw SettingNotFoundException(name)
         }
     }
+
     /**
      * Convenience function for updating a single settings value as an integer. This will either
      * create a new entry in the table if the given name does not exist, or modify the value of the
@@ -173,6 +323,7 @@
     fun putInt(name: String, value: Int): Boolean {
         return putString(name, value.toString())
     }
+
     /**
      * Convenience function for retrieving a single secure settings value as a boolean. Note that
      * internally setting values are always stored as strings; this function converts the string to
@@ -186,6 +337,7 @@
     fun getBool(name: String, def: Boolean): Boolean {
         return getInt(name, if (def) 1 else 0) != 0
     }
+
     /**
      * Convenience function for retrieving a single secure settings value as a boolean. Note that
      * internally setting values are always stored as strings; this function converts the string to
@@ -203,6 +355,7 @@
     fun getBool(name: String): Boolean {
         return getInt(name) != 0
     }
+
     /**
      * Convenience function for updating a single settings value as a boolean. This will either
      * create a new entry in the table if the given name does not exist, or modify the value of the
@@ -216,6 +369,7 @@
     fun putBool(name: String, value: Boolean): Boolean {
         return putInt(name, if (value) 1 else 0)
     }
+
     /**
      * Convenience function for retrieving a single secure settings value as a `long`. Note that
      * internally setting values are always stored as strings; this function converts the string to
@@ -230,6 +384,7 @@
         val valString = getString(name)
         return parseLongOrUseDefault(valString, def)
     }
+
     /**
      * Convenience function for retrieving a single secure settings value as a `long`. Note that
      * internally setting values are always stored as strings; this function converts the string to
@@ -248,6 +403,7 @@
         val valString = getString(name)
         return parseLongOrThrow(name, valString)
     }
+
     /**
      * Convenience function for updating a secure settings value as a long integer. This will either
      * create a new entry in the table if the given name does not exist, or modify the value of the
@@ -261,6 +417,7 @@
     fun putLong(name: String, value: Long): Boolean {
         return putString(name, value.toString())
     }
+
     /**
      * Convenience function for retrieving a single secure settings value as a floating point
      * number. Note that internally setting values are always stored as strings; this function
@@ -275,6 +432,7 @@
         val v = getString(name)
         return parseFloat(v, def)
     }
+
     /**
      * Convenience function for retrieving a single secure settings value as a float. Note that
      * internally setting values are always stored as strings; this function converts the string to
@@ -293,6 +451,7 @@
         val v = getString(name)
         return parseFloatOrThrow(name, v)
     }
+
     /**
      * Convenience function for updating a single settings value as a floating point number. This
      * will either create a new entry in the table if the given name does not exist, or modify the
@@ -306,6 +465,7 @@
     fun putFloat(name: String, value: Float): Boolean {
         return putString(name, value.toString())
     }
+
     companion object {
         /** Convert a string to a long, or uses a default if the string is malformed or null */
         @JvmStatic
@@ -319,6 +479,7 @@
                 }
             return value
         }
+
         /** Convert a string to a long, or throws an exception if the string is malformed or null */
         @JvmStatic
         @Throws(SettingNotFoundException::class)
@@ -332,6 +493,7 @@
                 throw SettingNotFoundException(name)
             }
         }
+
         /** Convert a string to a float, or uses a default if the string is malformed or null */
         @JvmStatic
         fun parseFloat(v: String?, def: Float): Float {
@@ -341,6 +503,7 @@
                 def
             }
         }
+
         /**
          * Convert a string to a float, or throws an exception if the string is malformed or null
          */
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
index 658b299..68cc753 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
@@ -22,18 +22,24 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.settings.UserTracker;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+
 import javax.inject.Inject;
 
 class SystemSettingsImpl implements SystemSettings {
     private final ContentResolver mContentResolver;
     private final UserTracker mUserTracker;
+    private final CoroutineDispatcher mBgCoroutineDispatcher;
 
     @Inject
-    SystemSettingsImpl(ContentResolver contentResolver, UserTracker userTracker) {
+    SystemSettingsImpl(ContentResolver contentResolver, UserTracker userTracker,
+            @Background CoroutineDispatcher bgDispatcher) {
         mContentResolver = contentResolver;
         mUserTracker = userTracker;
+        mBgCoroutineDispatcher = bgDispatcher;
     }
 
     @Override
@@ -52,6 +58,11 @@
     }
 
     @Override
+    public CoroutineDispatcher getBackgroundDispatcher() {
+        return mBgCoroutineDispatcher;
+    }
+
+    @Override
     public String getStringForUser(String name, int userHandle) {
         return Settings.System.getStringForUser(mContentResolver, name,
                 getRealUserHandle(userHandle));
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index ed13943..9133fbb 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -16,6 +16,7 @@
 package com.android.systemui.util.settings
 
 import android.annotation.UserIdInt
+import android.content.ContentResolver
 import android.database.ContentObserver
 import android.net.Uri
 import android.os.UserHandle
@@ -26,6 +27,9 @@
 import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloatOrThrow
 import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrThrow
 import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrUseDefault
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 /**
  * Used to interact with per-user Settings.Secure and Settings.System settings (but not
@@ -51,6 +55,7 @@
                 "userId cannot be set in interface, use setter from an implementation instead."
             )
         }
+
     /**
      * Returns the actual current user handle when querying with the current user. Otherwise,
      * returns the passed in user id.
@@ -60,9 +65,21 @@
             userHandle
         } else userTracker.userId
     }
+
     override fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) {
         registerContentObserverForUserSync(uri, settingsObserver, userId)
     }
+
+    override suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) =
+        withContext(getBackgroundDispatcher()) {
+            registerContentObserverForUserSync(uri, settingsObserver, userId)
+        }
+
+    override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) =
+        CoroutineScope(getBackgroundDispatcher()).launch {
+            registerContentObserverForUserSync(uri, settingsObserver, userId)
+        }
+
     /** Convenience wrapper around [ContentResolver.registerContentObserver].' */
     override fun registerContentObserverSync(
         uri: Uri,
@@ -71,6 +88,30 @@
     ) {
         registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId)
     }
+
+    override suspend fun registerContentObserver(
+        uri: Uri,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver
+    ) =
+        withContext(getBackgroundDispatcher()) {
+            registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId)
+        }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * API corresponding to [registerContentObserverForUser] for Java usage.
+     */
+    override fun registerContentObserverAsync(
+        uri: Uri,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver
+    ) =
+        CoroutineScope(getBackgroundDispatcher()).launch {
+            registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId)
+        }
+
     /**
      * Convenience wrapper around [ContentResolver.registerContentObserver]
      *
@@ -83,6 +124,37 @@
     ) {
         registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle)
     }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * suspend API corresponding to [registerContentObserverForUser] to ensure that
+     * [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an
+     * async block if they wish to synchronize execution.
+     */
+    suspend fun registerContentObserverForUser(
+        name: String,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) =
+        withContext(getBackgroundDispatcher()) {
+            registerContentObserverForUserSync(name, settingsObserver, userHandle)
+        }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * API corresponding to [registerContentObserverForUser] for Java usage.
+     */
+    fun registerContentObserverForUserAsync(
+        name: String,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) =
+        CoroutineScope(getBackgroundDispatcher()).launch {
+            registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle)
+        }
+
     /** Convenience wrapper around [ContentResolver.registerContentObserver] */
     fun registerContentObserverForUserSync(
         uri: Uri,
@@ -91,6 +163,37 @@
     ) {
         registerContentObserverForUserSync(uri, false, settingsObserver, userHandle)
     }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * suspend API corresponding to [registerContentObserverForUser] to ensure that
+     * [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an
+     * async block if they wish to synchronize execution.
+     */
+    suspend fun registerContentObserverForUser(
+        uri: Uri,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) =
+        withContext(getBackgroundDispatcher()) {
+            registerContentObserverForUserSync(uri, settingsObserver, userHandle)
+        }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * API corresponding to [registerContentObserverForUser] for Java usage.
+     */
+    fun registerContentObserverForUserAsync(
+        uri: Uri,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) =
+        CoroutineScope(getBackgroundDispatcher()).launch {
+            registerContentObserverForUserSync(uri, settingsObserver, userHandle)
+        }
+
     /**
      * Convenience wrapper around [ContentResolver.registerContentObserver]
      *
@@ -109,6 +212,50 @@
             userHandle
         )
     }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * suspend API corresponding to [registerContentObserverForUser] to ensure that
+     * [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an
+     * async block if they wish to synchronize execution.
+     */
+    suspend fun registerContentObserverForUser(
+        name: String,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) =
+        withContext(getBackgroundDispatcher()) {
+            registerContentObserverForUserSync(
+                name,
+                notifyForDescendants,
+                settingsObserver,
+                userHandle
+            )
+        }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * API corresponding to [registerContentObserverForUser] for Java usage.
+     */
+    fun registerContentObserverForUserAsync(
+        name: String,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) {
+        CoroutineScope(getBackgroundDispatcher()).launch {
+            registerContentObserverForUserSync(
+                getUriFor(name),
+                notifyForDescendants,
+                settingsObserver,
+                userHandle
+            )
+        }
+    }
+
     /** Convenience wrapper around [ContentResolver.registerContentObserver] */
     fun registerContentObserverForUserSync(
         uri: Uri,
@@ -127,6 +274,49 @@
             Unit
         }
     }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * suspend API corresponding to [registerContentObserverForUser] to ensure that
+     * [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an
+     * async block if they wish to synchronize execution.
+     */
+    suspend fun registerContentObserverForUser(
+        uri: Uri,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) =
+        withContext(getBackgroundDispatcher()) {
+            registerContentObserverForUserSync(
+                uri,
+                notifyForDescendants,
+                settingsObserver,
+                getRealUserHandle(userHandle)
+            )
+        }
+
+    /**
+     * Convenience wrapper around [ContentResolver.registerContentObserver].'
+     *
+     * API corresponding to [registerContentObserverForUser] for Java usage.
+     */
+    fun registerContentObserverForUserAsync(
+        uri: Uri,
+        notifyForDescendants: Boolean,
+        settingsObserver: ContentObserver,
+        userHandle: Int
+    ) =
+        CoroutineScope(getBackgroundDispatcher()).launch {
+            registerContentObserverForUserSync(
+                uri,
+                notifyForDescendants,
+                settingsObserver,
+                userHandle
+            )
+        }
+
     /**
      * Look up a name in the database.
      *
@@ -136,8 +326,10 @@
     override fun getString(name: String): String {
         return getStringForUser(name, userId)
     }
+
     /** See [getString]. */
     fun getStringForUser(name: String, userHandle: Int): String
+
     /**
      * Store a name/value pair into the database. Values written by this method will be overridden
      * if a restore happens in the future.
@@ -147,11 +339,14 @@
      * @return true if the value was set, false on database errors
      */
     fun putString(name: String, value: String, overrideableByRestore: Boolean): Boolean
+
     override fun putString(name: String, value: String): Boolean {
         return putStringForUser(name, value, userId)
     }
+
     /** Similar implementation to [putString] for the specified [userHandle]. */
     fun putStringForUser(name: String, value: String, userHandle: Int): Boolean
+
     /** Similar implementation to [putString] for the specified [userHandle]. */
     fun putStringForUser(
         name: String,
@@ -161,9 +356,11 @@
         @UserIdInt userHandle: Int,
         overrideableByRestore: Boolean
     ): Boolean
+
     override fun getInt(name: String, def: Int): Int {
         return getIntForUser(name, def, userId)
     }
+
     /** Similar implementation to [getInt] for the specified [userHandle]. */
     fun getIntForUser(name: String, def: Int, userHandle: Int): Int {
         val v = getStringForUser(name, userHandle)
@@ -173,8 +370,10 @@
             def
         }
     }
+
     @Throws(SettingNotFoundException::class)
     override fun getInt(name: String) = getIntForUser(name, userId)
+
     /** Similar implementation to [getInt] for the specified [userHandle]. */
     @Throws(SettingNotFoundException::class)
     fun getIntForUser(name: String, userHandle: Int): Int {
@@ -185,52 +384,66 @@
             throw SettingNotFoundException(name)
         }
     }
+
     override fun putInt(name: String, value: Int) = putIntForUser(name, value, userId)
+
     /** Similar implementation to [getInt] for the specified [userHandle]. */
     fun putIntForUser(name: String, value: Int, userHandle: Int) =
         putStringForUser(name, value.toString(), userHandle)
+
     override fun getBool(name: String, def: Boolean) = getBoolForUser(name, def, userId)
+
     /** Similar implementation to [getBool] for the specified [userHandle]. */
     fun getBoolForUser(name: String, def: Boolean, userHandle: Int) =
         getIntForUser(name, if (def) 1 else 0, userHandle) != 0
+
     @Throws(SettingNotFoundException::class)
     override fun getBool(name: String) = getBoolForUser(name, userId)
+
     /** Similar implementation to [getBool] for the specified [userHandle]. */
     @Throws(SettingNotFoundException::class)
     fun getBoolForUser(name: String, userHandle: Int): Boolean {
         return getIntForUser(name, userHandle) != 0
     }
+
     override fun putBool(name: String, value: Boolean): Boolean {
         return putBoolForUser(name, value, userId)
     }
+
     /** Similar implementation to [putBool] for the specified [userHandle]. */
     fun putBoolForUser(name: String, value: Boolean, userHandle: Int) =
         putIntForUser(name, if (value) 1 else 0, userHandle)
+
     /** Similar implementation to [getLong] for the specified [userHandle]. */
     fun getLongForUser(name: String, def: Long, userHandle: Int): Long {
         val valString = getStringForUser(name, userHandle)
         return parseLongOrUseDefault(valString, def)
     }
+
     /** Similar implementation to [getLong] for the specified [userHandle]. */
     @Throws(SettingNotFoundException::class)
     fun getLongForUser(name: String, userHandle: Int): Long {
         val valString = getStringForUser(name, userHandle)
         return parseLongOrThrow(name, valString)
     }
+
     /** Similar implementation to [putLong] for the specified [userHandle]. */
     fun putLongForUser(name: String, value: Long, userHandle: Int) =
         putStringForUser(name, value.toString(), userHandle)
+
     /** Similar implementation to [getFloat] for the specified [userHandle]. */
     fun getFloatForUser(name: String, def: Float, userHandle: Int): Float {
         val v = getStringForUser(name, userHandle)
         return parseFloat(v, def)
     }
+
     /** Similar implementation to [getFloat] for the specified [userHandle]. */
     @Throws(SettingNotFoundException::class)
     fun getFloatForUser(name: String, userHandle: Int): Float {
         val v = getStringForUser(name, userHandle)
         return parseFloatOrThrow(name, v)
     }
+
     /** Similar implementation to [putFloat] for the specified [userHandle]. */
     fun putFloatForUser(name: String, value: Float, userHandle: Int) =
         putStringForUser(name, value.toString(), userHandle)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
index eb11e38..92dd391 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
@@ -27,6 +27,12 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
@@ -41,11 +47,16 @@
 @TestableLooper.RunWithLooper
 class SettingsProxyTest : SysuiTestCase() {
 
+    private val testDispatcher = StandardTestDispatcher()
+
     private lateinit var mSettings: SettingsProxy
     private lateinit var mContentObserver: ContentObserver
+    private lateinit var testScope: TestScope
 
     @Before
     fun setUp() {
+        testScope = TestScope(testDispatcher)
+        Dispatchers.setMain(testDispatcher)
         mSettings = FakeSettingsProxy()
         mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {}
     }
@@ -58,6 +69,23 @@
     }
 
     @Test
+    fun registerContentObserverSuspend_inputString_success() =
+        testScope.runTest {
+            mSettings.registerContentObserver(TEST_SETTING, mContentObserver)
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
+        }
+
+    @Test
+    fun registerContentObserverAsync_inputString_success() {
+        mSettings.registerContentObserverAsync(TEST_SETTING, mContentObserver)
+        testScope.launch {
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
+        }
+    }
+
+    @Test
     fun registerContentObserver_inputString_notifyForDescendants_true() {
         mSettings.registerContentObserverSync(
             TEST_SETTING,
@@ -69,6 +97,31 @@
     }
 
     @Test
+    fun registerContentObserverSuspend_inputString_notifyForDescendants_true() =
+        testScope.runTest {
+            mSettings.registerContentObserver(
+                TEST_SETTING,
+                notifyForDescendants = true,
+                mContentObserver
+            )
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
+        }
+
+    @Test
+    fun registerContentObserverAsync_inputString_notifyForDescendants_true() {
+        mSettings.registerContentObserverAsync(
+            TEST_SETTING,
+            notifyForDescendants = true,
+            mContentObserver
+        )
+        testScope.launch {
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
+        }
+    }
+
+    @Test
     fun registerContentObserver_inputUri_success() {
         mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
         verify(mSettings.getContentResolver())
@@ -76,6 +129,23 @@
     }
 
     @Test
+    fun registerContentObserverSuspend_inputUri_success() =
+        testScope.runTest {
+            mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver)
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
+        }
+
+    @Test
+    fun registerContentObserverAsync_inputUri_success() {
+        mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver)
+        testScope.launch {
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver))
+        }
+    }
+
+    @Test
     fun registerContentObserver_inputUri_notifyForDescendants_true() {
         mSettings.registerContentObserverSync(
             TEST_SETTING_URI,
@@ -87,12 +157,52 @@
     }
 
     @Test
-    fun unregisterContentObserver() {
+    fun registerContentObserverSuspend_inputUri_notifyForDescendants_true() =
+        testScope.runTest {
+            mSettings.registerContentObserver(
+                TEST_SETTING_URI,
+                notifyForDescendants = true,
+                mContentObserver
+            )
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
+        }
+
+    @Test
+    fun registerContentObserverAsync_inputUri_notifyForDescendants_true() {
+        mSettings.registerContentObserverAsync(
+            TEST_SETTING_URI,
+            notifyForDescendants = true,
+            mContentObserver
+        )
+        testScope.launch {
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver))
+        }
+    }
+
+    @Test
+    fun unregisterContentObserverSync() {
         mSettings.unregisterContentObserverSync(mContentObserver)
         verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver))
     }
 
     @Test
+    fun unregisterContentObserverSuspend_inputString_success() =
+        testScope.runTest {
+            mSettings.unregisterContentObserver(mContentObserver)
+            verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver))
+        }
+
+    @Test
+    fun unregisterContentObserverAsync_inputString_success() {
+        mSettings.unregisterContentObserverAsync(mContentObserver)
+        testScope.launch {
+            verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver))
+        }
+    }
+
+    @Test
     fun getString_keyPresent_returnValidValue() {
         mSettings.putString(TEST_SETTING, "test")
         assertThat(mSettings.getString(TEST_SETTING)).isEqualTo("test")
@@ -203,12 +313,15 @@
 
         private val mContentResolver = mock(ContentResolver::class.java)
         private val settingToValueMap: MutableMap<String, String> = mutableMapOf()
+        private val testDispatcher = StandardTestDispatcher()
 
         override fun getContentResolver() = mContentResolver
 
         override fun getUriFor(name: String) =
             Uri.parse(StringBuilder().append("content://settings/").append(name).toString())
 
+        override fun getBackgroundDispatcher() = testDispatcher
+
         override fun getString(name: String): String {
             return settingToValueMap[name] ?: ""
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
index 38469ee..4cb5fa9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
@@ -30,6 +30,12 @@
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.settings.UserTracker
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
@@ -47,6 +53,7 @@
     private var mUserTracker = FakeUserTracker()
     private var mSettings: UserSettingsProxy = FakeUserSettingsProxy(mUserTracker)
     private var mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {}
+    private lateinit var testScope: TestScope
 
     @Before
     fun setUp() {
@@ -54,6 +61,9 @@
             listOf(UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_MAIN)),
             selectedUserIndex = 0
         )
+        val testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
+        Dispatchers.setMain(testDispatcher)
     }
 
     @Test
@@ -73,6 +83,41 @@
     }
 
     @Test
+    fun registerContentObserverForUserSuspend_inputString_success() =
+        testScope.runTest {
+            mSettings.registerContentObserverForUser(
+                TEST_SETTING,
+                mContentObserver,
+                mUserTracker.userId
+            )
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(
+                    eq(TEST_SETTING_URI),
+                    eq(false),
+                    eq(mContentObserver),
+                    eq(MAIN_USER_ID)
+                )
+        }
+
+    @Test
+    fun registerContentObserverForUserAsync_inputString_success() {
+        mSettings.registerContentObserverForUserAsync(
+            TEST_SETTING,
+            mContentObserver,
+            mUserTracker.userId
+        )
+        testScope.launch {
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(
+                    eq(TEST_SETTING_URI),
+                    eq(false),
+                    eq(mContentObserver),
+                    eq(MAIN_USER_ID)
+                )
+        }
+    }
+
+    @Test
     fun registerContentObserverForUser_inputString_notifyForDescendants_true() {
         mSettings.registerContentObserverForUserSync(
             TEST_SETTING,
@@ -90,6 +135,45 @@
     }
 
     @Test
+    fun registerContentObserverForUserSuspend_inputString_notifyForDescendants_true() =
+        testScope.runTest {
+            mSettings.registerContentObserverForUser(
+                TEST_SETTING,
+                notifyForDescendants = true,
+                mContentObserver,
+                mUserTracker.userId
+            )
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(
+                    eq(TEST_SETTING_URI),
+                    eq(
+                        true,
+                    ),
+                    eq(mContentObserver),
+                    eq(MAIN_USER_ID)
+                )
+        }
+
+    @Test
+    fun registerContentObserverForUserAsync_inputString_notifyForDescendants_true() {
+        mSettings.registerContentObserverForUserAsync(
+            TEST_SETTING,
+            notifyForDescendants = true,
+            mContentObserver,
+            mUserTracker.userId
+        )
+        testScope.launch {
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(
+                    eq(TEST_SETTING_URI),
+                    eq(true),
+                    eq(mContentObserver),
+                    eq(MAIN_USER_ID)
+                )
+        }
+    }
+
+    @Test
     fun registerContentObserverForUser_inputUri_success() {
         mSettings.registerContentObserverForUserSync(
             TEST_SETTING_URI,
@@ -106,6 +190,41 @@
     }
 
     @Test
+    fun registerContentObserverForUserSuspend_inputUri_success() =
+        testScope.runTest {
+            mSettings.registerContentObserverForUser(
+                TEST_SETTING_URI,
+                mContentObserver,
+                mUserTracker.userId
+            )
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(
+                    eq(TEST_SETTING_URI),
+                    eq(false),
+                    eq(mContentObserver),
+                    eq(MAIN_USER_ID)
+                )
+        }
+
+    @Test
+    fun registerContentObserverForUserAsync_inputUri_success() {
+        mSettings.registerContentObserverForUserAsync(
+            TEST_SETTING_URI,
+            mContentObserver,
+            mUserTracker.userId
+        )
+        testScope.launch {
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(
+                    eq(TEST_SETTING_URI),
+                    eq(false),
+                    eq(mContentObserver),
+                    eq(MAIN_USER_ID)
+                )
+        }
+    }
+
+    @Test
     fun registerContentObserverForUser_inputUri_notifyForDescendants_true() {
         mSettings.registerContentObserverForUserSync(
             TEST_SETTING_URI,
@@ -123,6 +242,45 @@
     }
 
     @Test
+    fun registerContentObserverForUserSuspend_inputUri_notifyForDescendants_true() =
+        testScope.runTest {
+            mSettings.registerContentObserverForUser(
+                TEST_SETTING_URI,
+                notifyForDescendants = true,
+                mContentObserver,
+                mUserTracker.userId
+            )
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(
+                    eq(TEST_SETTING_URI),
+                    eq(
+                        true,
+                    ),
+                    eq(mContentObserver),
+                    eq(MAIN_USER_ID)
+                )
+        }
+
+    @Test
+    fun registerContentObserverForUserAsync_inputUri_notifyForDescendants_true() {
+        mSettings.registerContentObserverForUserAsync(
+            TEST_SETTING_URI,
+            notifyForDescendants = true,
+            mContentObserver,
+            mUserTracker.userId
+        )
+        testScope.launch {
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(
+                    eq(TEST_SETTING_URI),
+                    eq(true),
+                    eq(mContentObserver),
+                    eq(MAIN_USER_ID)
+                )
+        }
+    }
+
+    @Test
     fun registerContentObserver_inputUri_success() {
         mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver)
         verify(mSettings.getContentResolver())
@@ -130,6 +288,33 @@
     }
 
     @Test
+    fun registerContentObserverSuspend_inputUri_success() =
+        testScope.runTest {
+            mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver)
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(
+                    eq(TEST_SETTING_URI),
+                    eq(false),
+                    eq(mContentObserver),
+                    eq(0)
+                )
+        }
+
+    @Test
+    fun registerContentObserverAsync_inputUri_success() {
+        mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver)
+        testScope.launch {
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(
+                    eq(TEST_SETTING_URI),
+                    eq(false),
+                    eq(mContentObserver),
+                    eq(0)
+                )
+        }
+    }
+
+    @Test
     fun registerContentObserver_inputUri_notifyForDescendants_true() {
         mSettings.registerContentObserverSync(
             TEST_SETTING_URI,
@@ -141,6 +326,33 @@
     }
 
     @Test
+    fun registerContentObserverSuspend_inputUri_notifyForDescendants_true() =
+        testScope.runTest {
+            mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver)
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(
+                    eq(TEST_SETTING_URI),
+                    eq(false),
+                    eq(mContentObserver),
+                    eq(0)
+                )
+        }
+
+    @Test
+    fun registerContentObserverAsync_inputUri_notifyForDescendants_true() {
+        mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver)
+        testScope.launch {
+            verify(mSettings.getContentResolver())
+                .registerContentObserver(
+                    eq(TEST_SETTING_URI),
+                    eq(false),
+                    eq(mContentObserver),
+                    eq(0)
+                )
+        }
+    }
+
+    @Test
     fun getString_keyPresent_returnValidValue() {
         mSettings.putString(TEST_SETTING, "test")
         assertThat(mSettings.getString(TEST_SETTING)).isEqualTo("test")
@@ -305,12 +517,15 @@
         private val mContentResolver = mock(ContentResolver::class.java)
         private val userIdToSettingsValueMap: MutableMap<Int, MutableMap<String, String>> =
             mutableMapOf()
+        private val testDispatcher = StandardTestDispatcher()
 
         override fun getContentResolver() = mContentResolver
 
         override fun getUriFor(name: String) =
             Uri.parse(StringBuilder().append(URI_PREFIX).append(name).toString())
 
+        override fun getBackgroundDispatcher() = testDispatcher
+
         override fun getStringForUser(name: String, userHandle: Int) =
             userIdToSettingsValueMap[userHandle]?.get(name) ?: ""
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
index 3a70cdf..136b129 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java
@@ -22,6 +22,8 @@
 import android.database.ContentObserver;
 import android.net.Uri;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -44,6 +46,13 @@
     }
 
     @Override
+    public CoroutineDispatcher getBackgroundDispatcher() {
+        throw new UnsupportedOperationException(
+                "GlobalSettings.getBackgroundDispatcher is not implemented, but you may find "
+                        + "GlobalSettings.getBackgroundDispatcher helpful instead.");
+    }
+
+    @Override
     public void registerContentObserverSync(Uri uri, boolean notifyDescendants,
             ContentObserver settingsObserver) {
         List<ContentObserver> observers;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
index cd219ec..6cefa34 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
@@ -27,6 +27,8 @@
 
 import com.android.systemui.settings.UserTracker;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -66,6 +68,11 @@
     }
 
     @Override
+    public CoroutineDispatcher getBackgroundDispatcher() {
+        return null;
+    }
+
+    @Override
     public void registerContentObserverForUserSync(Uri uri, boolean notifyDescendants,
             ContentObserver settingsObserver, int userHandle) {
         List<ContentObserver> observers;