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;