Seperate SettingsConfigProvider into producers.

This provides a clear separation between the data producing classes and
the core library class. It also allows seperating the logic in
SettingsConfigProvider into different producers that provide their data
from different sources (for ex, settings vs resources).

Bug: 173428759
Test: Manual - flash device and verify
Test: ./gradlew window:window:test

Change-Id: I111058ff7c3479bc53aede0b29de1e2e1c2ff91f
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp
index d8f00bb..dc4b563 100644
--- a/libs/WindowManager/Jetpack/Android.bp
+++ b/libs/WindowManager/Jetpack/Android.bp
@@ -30,13 +30,20 @@
 
 java_library {
     name: "androidx.window.sidecar",
-    srcs: ["src/androidx/window/sidecar/**/*.java", "src/androidx/window/util/**/*.java"],
+    srcs: [
+        "src/androidx/window/sidecar/**/*.java",
+        "src/androidx/window/util/**/*.java",
+        "src/androidx/window/common/**/*.java",
+    ],
     static_libs: ["window-sidecar"],
     installable: true,
     sdk_version: "core_platform",
     system_ext_specific: true,
-    libs: ["framework", "androidx.annotation_annotation",],
-    required: ["androidx.window.sidecar.xml",],
+    libs: [
+        "framework",
+        "androidx.annotation_annotation",
+    ],
+    required: ["androidx.window.sidecar.xml"],
 }
 
 prebuilt_etc {
@@ -58,13 +65,20 @@
 
 java_library {
     name: "androidx.window.extensions",
-    srcs: ["src/androidx/window/extensions/**/*.java", "src/androidx/window/util/**/*.java"],
+    srcs: [
+        "src/androidx/window/extensions/**/*.java",
+        "src/androidx/window/util/**/*.java",
+        "src/androidx/window/common/**/*.java",
+    ],
     static_libs: ["window-extensions"],
     installable: true,
     sdk_version: "core_platform",
     system_ext_specific: true,
-    libs: ["framework", "androidx.annotation_annotation",],
-    required: ["androidx.window.extensions.xml",],
+    libs: [
+        "framework",
+        "androidx.annotation_annotation",
+    ],
+    required: ["androidx.window.extensions.xml"],
 }
 
 prebuilt_etc {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java
new file mode 100644
index 0000000..e6ad011
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonDisplayFeature.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.common;
+
+import static androidx.window.util.ExtensionHelper.isZero;
+
+import android.annotation.Nullable;
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */
+final class CommonDisplayFeature implements DisplayFeature {
+    private static final Pattern FEATURE_PATTERN =
+            Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]");
+
+    private static final String FEATURE_TYPE_FOLD = "fold";
+    private static final String FEATURE_TYPE_HINGE = "hinge";
+
+    // TODO(b/183049815): Support feature strings that include the state of the feature.
+    /**
+     * Parses a display feature from a string.
+     *
+     * @throws IllegalArgumentException if the provided string is improperly formatted or could not
+     * otherwise be parsed.
+     *
+     * @see #FEATURE_PATTERN
+     */
+    @NonNull
+    static CommonDisplayFeature parseFromString(@NonNull String string) {
+        Matcher featureMatcher = FEATURE_PATTERN.matcher(string);
+        if (!featureMatcher.matches()) {
+            throw new IllegalArgumentException("Malformed feature description format: " + string);
+        }
+        try {
+            String featureType = featureMatcher.group(1);
+            int type;
+            switch (featureType) {
+                case FEATURE_TYPE_FOLD:
+                    type = 1 /* TYPE_FOLD */;
+                    break;
+                case FEATURE_TYPE_HINGE:
+                    type = 2 /* TYPE_HINGE */;
+                    break;
+                default: {
+                    throw new IllegalArgumentException("Malformed feature type: " + featureType);
+                }
+            }
+
+            int left = Integer.parseInt(featureMatcher.group(2));
+            int top = Integer.parseInt(featureMatcher.group(3));
+            int right = Integer.parseInt(featureMatcher.group(4));
+            int bottom = Integer.parseInt(featureMatcher.group(5));
+            Rect featureRect = new Rect(left, top, right, bottom);
+            if (isZero(featureRect)) {
+                throw new IllegalArgumentException("Feature has empty bounds: " + string);
+            }
+
+            return new CommonDisplayFeature(type, null, featureRect);
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("Malformed feature description: " + string, e);
+        }
+    }
+
+    private final int mType;
+    @Nullable
+    private final Integer mState;
+    @NonNull
+    private final Rect mRect;
+
+    CommonDisplayFeature(int type, @Nullable Integer state, @NonNull Rect rect) {
+        this.mType = type;
+        this.mState = state;
+        if (rect.width() == 0 && rect.height() == 0) {
+            throw new IllegalArgumentException(
+                    "Display feature rectangle cannot have zero width and height simultaneously.");
+        }
+        this.mRect = rect;
+    }
+
+    public int getType() {
+        return mType;
+    }
+
+    /** Returns the state of the feature, or {@code null} if the feature has no state. */
+    @Nullable
+    public Integer getState() {
+        return mState;
+    }
+
+    @NonNull
+    public Rect getRect() {
+        return mRect;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        CommonDisplayFeature that = (CommonDisplayFeature) o;
+        return mType == that.mType
+                && Objects.equals(mState, that.mState)
+                && mRect.equals(that.mRect);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mType, mState, mRect);
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java
new file mode 100644
index 0000000..b6c4c43
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DisplayFeature.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.common;
+
+import android.annotation.Nullable;
+import android.graphics.Rect;
+
+import androidx.annotation.NonNull;
+
+/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */
+public interface DisplayFeature {
+    /** Returns the type of the feature. */
+    int getType();
+
+    /** Returns the state of the feature, or {@code null} if the feature has no state. */
+    @Nullable
+    Integer getState();
+
+    /** Returns the bounds of the feature. */
+    @NonNull
+    Rect getRect();
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java
new file mode 100644
index 0000000..cd2cadc
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/ResourceConfigDisplayFeatureProducer.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.common;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.window.util.BaseDataProducer;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Implementation of {@link androidx.window.util.DataProducer} that produces
+ * {@link CommonDisplayFeature} parsed from a string stored in the resources config at
+ * {@link R.string#config_display_features}.
+ */
+public final class ResourceConfigDisplayFeatureProducer extends
+        BaseDataProducer<List<DisplayFeature>> {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "ResourceConfigDisplayFeatureProducer";
+
+    private final Context mContext;
+
+    public ResourceConfigDisplayFeatureProducer(@NonNull Context context) {
+        mContext = context;
+    }
+
+    @Override
+    @Nullable
+    public Optional<List<DisplayFeature>> getData() {
+        String displayFeaturesString = mContext.getResources().getString(
+                R.string.config_display_features);
+        if (TextUtils.isEmpty(displayFeaturesString)) {
+            return Optional.empty();
+        }
+
+        List<DisplayFeature> features = new ArrayList<>();
+        String[] featureStrings =  displayFeaturesString.split(";");
+        for (String featureString : featureStrings) {
+            CommonDisplayFeature feature;
+            try {
+                feature = CommonDisplayFeature.parseFromString(featureString);
+            } catch (IllegalArgumentException e) {
+                if (DEBUG) {
+                    Log.w(TAG, "Failed to parse display feature: " + featureString, e);
+                }
+                continue;
+            }
+            features.add(feature);
+        }
+        return Optional.of(features);
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java
new file mode 100644
index 0000000..2026df3
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDevicePostureProducer.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.common;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import androidx.window.util.BaseDataProducer;
+
+import java.util.Optional;
+
+/**
+ * Implementation of {@link androidx.window.util.DataProducer} that provides the device posture
+ * as an {@link Integer} from a value stored in {@link Settings}.
+ */
+public final class SettingsDevicePostureProducer extends BaseDataProducer<Integer> {
+    private static final String DEVICE_POSTURE = "device_posture";
+
+    private final Uri mDevicePostureUri =
+            Settings.Global.getUriFor(DEVICE_POSTURE);
+
+    private final ContentResolver mResolver;
+    private final ContentObserver mObserver;
+    private boolean mRegisteredObservers;
+
+    public SettingsDevicePostureProducer(@NonNull Context context) {
+        mResolver = context.getContentResolver();
+        mObserver = new SettingsObserver();
+    }
+
+    @Override
+    @Nullable
+    public Optional<Integer> getData() {
+        int posture = Settings.Global.getInt(mResolver, DEVICE_POSTURE, -1);
+        return posture == -1 ? Optional.empty() : Optional.of(posture);
+    }
+
+    /**
+     * Registers settings observers, if needed. When settings observers are registered for this
+     * producer callbacks for changes in data will be triggered.
+     */
+    public void registerObserversIfNeeded() {
+        if (mRegisteredObservers) {
+            return;
+        }
+        mRegisteredObservers = true;
+        mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendants */,
+                mObserver /* ContentObserver */);
+    }
+
+    /**
+     * Unregisters settings observers, if needed. When settings observers are unregistered for this
+     * producer callbacks for changes in data will not be triggered.
+     */
+    public void unregisterObserversIfNeeded() {
+        if (!mRegisteredObservers) {
+            return;
+        }
+        mRegisteredObservers = false;
+        mResolver.unregisterContentObserver(mObserver);
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+        SettingsObserver() {
+            super(new Handler(Looper.getMainLooper()));
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (mDevicePostureUri.equals(uri)) {
+                notifyDataChanged();
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java
new file mode 100644
index 0000000..0406626
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/SettingsDisplayFeatureProducer.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.common;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.window.util.BaseDataProducer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Implementation of {@link androidx.window.util.DataProducer} that produces
+ * {@link CommonDisplayFeature} parsed from a string stored in {@link Settings}.
+ */
+public final class SettingsDisplayFeatureProducer
+        extends BaseDataProducer<List<DisplayFeature>> {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "SettingsDisplayFeatureProducer";
+    private static final String DISPLAY_FEATURES = "display_features";
+
+    private final Uri mDisplayFeaturesUri =
+            Settings.Global.getUriFor(DISPLAY_FEATURES);
+
+    private final ContentResolver mResolver;
+    private final ContentObserver mObserver;
+    private boolean mRegisteredObservers;
+
+    public SettingsDisplayFeatureProducer(@NonNull Context context) {
+        mResolver = context.getContentResolver();
+        mObserver = new SettingsObserver();
+    }
+
+    @Override
+    @Nullable
+    public Optional<List<DisplayFeature>> getData() {
+        String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES);
+        if (displayFeaturesString == null) {
+            return Optional.empty();
+        }
+
+        List<DisplayFeature> features = new ArrayList<>();
+        if (TextUtils.isEmpty(displayFeaturesString)) {
+            return Optional.of(features);
+        }
+        String[] featureStrings =  displayFeaturesString.split(";");
+
+        for (String featureString : featureStrings) {
+            CommonDisplayFeature feature;
+            try {
+                feature = CommonDisplayFeature.parseFromString(featureString);
+            } catch (IllegalArgumentException e) {
+                if (DEBUG) {
+                    Log.w(TAG, "Failed to parse display feature: " + featureString, e);
+                }
+                continue;
+            }
+            features.add(feature);
+        }
+        return Optional.of(features);
+    }
+
+    /**
+     * Registers settings observers, if needed. When settings observers are registered for this
+     * producer callbacks for changes in data will be triggered.
+     */
+    public void registerObserversIfNeeded() {
+        if (mRegisteredObservers) {
+            return;
+        }
+        mRegisteredObservers = true;
+        mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */,
+                mObserver /* ContentObserver */);
+    }
+
+    /**
+     * Unregisters settings observers, if needed. When settings observers are unregistered for this
+     * producer callbacks for changes in data will not be triggered.
+     */
+    public void unregisterObserversIfNeeded() {
+        if (!mRegisteredObservers) {
+            return;
+        }
+        mRegisteredObservers = false;
+        mResolver.unregisterContentObserver(mObserver);
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+        SettingsObserver() {
+            super(new Handler(Looper.getMainLooper()));
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (mDisplayFeaturesUri.equals(uri)) {
+                notifyDataChanged();
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
index 5c91cf41..67030fc 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
@@ -27,11 +27,16 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
-import androidx.window.util.BaseDisplayFeature;
-import androidx.window.util.SettingsConfigProvider;
+import androidx.window.common.DisplayFeature;
+import androidx.window.common.ResourceConfigDisplayFeatureProducer;
+import androidx.window.common.SettingsDevicePostureProducer;
+import androidx.window.common.SettingsDisplayFeatureProducer;
+import androidx.window.util.DataProducer;
+import androidx.window.util.PriorityDataProducer;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Reference implementation of androidx.window.extensions OEM interface for use with
@@ -41,23 +46,43 @@
  * production builds since the interface can still change before reaching stable version.
  * Please refer to {@link androidx.window.sidecar.SampleSidecarImpl} instead.
  */
-class SampleExtensionImpl extends StubExtension implements
-        SettingsConfigProvider.StateChangeCallback {
+class SampleExtensionImpl extends StubExtension {
     private static final String TAG = "SampleExtension";
 
-    private final SettingsConfigProvider mConfigProvider;
+    private final SettingsDevicePostureProducer mSettingsDevicePostureProducer;
+    private final DataProducer<Integer> mDevicePostureProducer;
+
+    private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
+    private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer;
 
     SampleExtensionImpl(Context context) {
-        mConfigProvider = new SettingsConfigProvider(context, this);
+        mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context);
+        mDevicePostureProducer = mSettingsDevicePostureProducer;
+
+        mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context);
+        mDisplayFeatureProducer = new PriorityDataProducer<>(List.of(
+                mSettingsDisplayFeatureProducer,
+                new ResourceConfigDisplayFeatureProducer(context)
+        ));
+
+        mDevicePostureProducer.addDataChangedCallback(this::onDevicePostureChanged);
+        mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
     }
 
-    @Override
-    public void onDevicePostureChanged() {
-        updateDeviceState(new ExtensionDeviceState(mConfigProvider.getDeviceState()));
+    private void onDevicePostureChanged() {
+        updateDeviceState(new ExtensionDeviceState(getDevicePosture()));
+
+        // Trigger a change in display features as the posture will be used in place of the feature
+        // state if the state is left unset by the producer.
+        onDisplayFeaturesChanged();
     }
 
-    @Override
-    public void onDisplayFeaturesChanged() {
+    private int getDevicePosture() {
+        Optional<Integer> posture = mDevicePostureProducer.getData();
+        return posture.orElse(ExtensionDeviceState.POSTURE_UNKNOWN);
+    }
+
+    private void onDisplayFeaturesChanged() {
         for (Activity activity : getActivitiesListeningForLayoutChanges()) {
             ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
             updateWindowLayout(activity, newLayout);
@@ -84,13 +109,20 @@
             return features;
         }
 
-        List<BaseDisplayFeature> storedFeatures = mConfigProvider.getDisplayFeatures();
-        for (BaseDisplayFeature baseFeature : storedFeatures) {
-            Rect featureRect = baseFeature.getRect();
-            rotateRectToDisplayRotation(displayId, featureRect);
-            transformToWindowSpaceRect(activity, featureRect);
-            features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(),
-                    baseFeature.getState()));
+        Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData();
+        if (storedFeatures.isPresent()) {
+            int posture = getDevicePosture();
+
+            for (DisplayFeature baseFeature : storedFeatures.get()) {
+                Rect featureRect = baseFeature.getRect();
+                rotateRectToDisplayRotation(displayId, featureRect);
+                transformToWindowSpaceRect(activity, featureRect);
+
+                Integer featureState = baseFeature.getState();
+
+                features.add(new ExtensionFoldingFeature(featureRect, baseFeature.getType(),
+                        featureState == null ? posture : featureState));
+            }
         }
         return features;
     }
@@ -98,9 +130,11 @@
     @Override
     protected void onListenersChanged() {
         if (hasListeners()) {
-            mConfigProvider.registerObserversIfNeeded();
+            mSettingsDevicePostureProducer.registerObserversIfNeeded();
+            mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
         } else {
-            mConfigProvider.unregisterObserversIfNeeded();
+            mSettingsDevicePostureProducer.unregisterObserversIfNeeded();
+            mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded();
         }
 
         onDevicePostureChanged();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index d3700f8..408c42f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -29,33 +29,49 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
-import androidx.window.util.BaseDisplayFeature;
-import androidx.window.util.SettingsConfigProvider;
+import androidx.window.common.DisplayFeature;
+import androidx.window.common.ResourceConfigDisplayFeatureProducer;
+import androidx.window.common.SettingsDevicePostureProducer;
+import androidx.window.common.SettingsDisplayFeatureProducer;
+import androidx.window.util.DataProducer;
+import androidx.window.util.PriorityDataProducer;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Reference implementation of androidx.window.sidecar OEM interface for use with
  * WindowManager Jetpack.
  */
-class SampleSidecarImpl extends StubSidecar implements
-        SettingsConfigProvider.StateChangeCallback {
+class SampleSidecarImpl extends StubSidecar {
     private static final String TAG = "SampleSidecar";
 
-    private final SettingsConfigProvider mConfigProvider;
+    private final SettingsDevicePostureProducer mSettingsDevicePostureProducer;
+    private final DataProducer<Integer> mDevicePostureProducer;
+
+    private final SettingsDisplayFeatureProducer mSettingsDisplayFeatureProducer;
+    private final DataProducer<List<DisplayFeature>> mDisplayFeatureProducer;
 
     SampleSidecarImpl(Context context) {
-        mConfigProvider = new SettingsConfigProvider(context, this);
+        mSettingsDevicePostureProducer = new SettingsDevicePostureProducer(context);
+        mDevicePostureProducer = mSettingsDevicePostureProducer;
+
+        mSettingsDisplayFeatureProducer = new SettingsDisplayFeatureProducer(context);
+        mDisplayFeatureProducer = new PriorityDataProducer<>(List.of(
+                mSettingsDisplayFeatureProducer,
+                new ResourceConfigDisplayFeatureProducer(context)
+        ));
+
+        mDevicePostureProducer.addDataChangedCallback(this::onDevicePostureChanged);
+        mDisplayFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
     }
 
-    @Override
-    public void onDevicePostureChanged() {
+    private void onDevicePostureChanged() {
         updateDeviceState(getDeviceState());
     }
 
-    @Override
-    public void onDisplayFeaturesChanged() {
+    private void onDisplayFeaturesChanged() {
         for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
             SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
             updateWindowLayout(windowToken, newLayout);
@@ -65,8 +81,10 @@
     @NonNull
     @Override
     public SidecarDeviceState getDeviceState() {
+        Optional<Integer> posture = mDevicePostureProducer.getData();
+
         SidecarDeviceState deviceState = new SidecarDeviceState();
-        deviceState.posture = mConfigProvider.getDeviceState();
+        deviceState.posture = posture.orElse(SidecarDeviceState.POSTURE_UNKNOWN);
         return deviceState;
     }
 
@@ -96,15 +114,17 @@
             return features;
         }
 
-        List<BaseDisplayFeature> storedFeatures = mConfigProvider.getDisplayFeatures();
-        for (BaseDisplayFeature baseFeature : storedFeatures) {
-            SidecarDisplayFeature feature = new SidecarDisplayFeature();
-            Rect featureRect = baseFeature.getRect();
-            rotateRectToDisplayRotation(displayId, featureRect);
-            transformToWindowSpaceRect(activity, featureRect);
-            feature.setRect(featureRect);
-            feature.setType(baseFeature.getType());
-            features.add(feature);
+        Optional<List<DisplayFeature>> storedFeatures = mDisplayFeatureProducer.getData();
+        if (storedFeatures.isPresent()) {
+            for (DisplayFeature baseFeature : storedFeatures.get()) {
+                SidecarDisplayFeature feature = new SidecarDisplayFeature();
+                Rect featureRect = baseFeature.getRect();
+                rotateRectToDisplayRotation(displayId, featureRect);
+                transformToWindowSpaceRect(activity, featureRect);
+                feature.setRect(featureRect);
+                feature.setType(baseFeature.getType());
+                features.add(feature);
+            }
         }
         return features;
     }
@@ -112,9 +132,11 @@
     @Override
     protected void onListenersChanged() {
         if (hasListeners()) {
-            mConfigProvider.registerObserversIfNeeded();
+            mSettingsDevicePostureProducer.registerObserversIfNeeded();
+            mSettingsDisplayFeatureProducer.registerObserversIfNeeded();
         } else {
-            mConfigProvider.unregisterObserversIfNeeded();
+            mSettingsDevicePostureProducer.unregisterObserversIfNeeded();
+            mSettingsDisplayFeatureProducer.unregisterObserversIfNeeded();
         }
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
new file mode 100644
index 0000000..0a46703451
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.util;
+
+import androidx.annotation.NonNull;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Base class that provides the implementation for the callback mechanism of the
+ * {@link DataProducer} API.
+ *
+ * @param <T> The type of data this producer returns through {@link #getData()}.
+ */
+public abstract class BaseDataProducer<T> implements DataProducer<T> {
+    private final Set<Runnable> mCallbacks = new LinkedHashSet<>();
+
+    @Override
+    public final void addDataChangedCallback(@NonNull Runnable callback) {
+        mCallbacks.add(callback);
+    }
+
+    @Override
+    public final void removeDataChangedCallback(@NonNull Runnable callback) {
+        mCallbacks.remove(callback);
+    }
+
+    /**
+     * Called to notify all registered callbacks that the data provided by {@link #getData()} has
+     * changed.
+     */
+    protected void notifyDataChanged() {
+        for (Runnable callback : mCallbacks) {
+            callback.run();
+        }
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java
deleted file mode 100644
index b74a2a4..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.util;
-
-import android.graphics.Rect;
-
-import androidx.annotation.NonNull;
-
-/** Wrapper for both Extension and Sidecar versions of DisplayFeature. */
-public class BaseDisplayFeature {
-    private final int mType;
-    private final int mState;
-    @NonNull
-    public final Rect mRect;
-
-    public BaseDisplayFeature(int type, int state, @NonNull Rect rect) {
-        this.mType = type;
-        this.mState = state;
-        if (rect.width() == 0 && rect.height() == 0) {
-            throw new IllegalArgumentException(
-                    "Display feature rectangle cannot have zero width and height simultaneously.");
-        }
-        this.mRect = rect;
-    }
-
-    public int getType() {
-        return mType;
-    }
-
-    public int getState() {
-        return mState;
-    }
-
-    @NonNull
-    public Rect getRect() {
-        return mRect;
-    }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
new file mode 100644
index 0000000..d4d1a23
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.util;
+
+import android.annotation.NonNull;
+
+import java.util.Optional;
+
+/**
+ * Produces data through {@link #getData()} and provides a mechanism for receiving a callback when
+ * the data managed by the produces has changed.
+ *
+ * @param <T> The type of data this producer returns through {@link #getData()}.
+ */
+public interface DataProducer<T> {
+    /**
+     * Returns the data currently stored in the provider, or {@link Optional#empty()} if the
+     * provider has no data.
+     */
+    Optional<T> getData();
+
+    /**
+     * Adds a callback to be notified when the data returned from {@link #getData()} has changed.
+     */
+    void addDataChangedCallback(@NonNull Runnable callback);
+
+    /** Removes a callback previously added with {@link #addDataChangedCallback(Runnable)}.  */
+    void removeDataChangedCallback(@NonNull Runnable callback);
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java
new file mode 100644
index 0000000..990ae20
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/PriorityDataProducer.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.util;
+
+import android.annotation.Nullable;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Implementation of {@link DataProducer} that delegates calls to {@link #getData()} to the list of
+ * provided child producers.
+ * <p>
+ * The value returned is based on the precedence of the supplied children where the producer with
+ * index 0 has a higher precedence than producers that come later in the list. When a producer with
+ * a higher precedence has a non-empty value returned from {@link #getData()}, its value will be
+ * returned from an instance of this class, ignoring all other producers with lower precedence.
+ *
+ * @param <T> The type of data this producer returns through {@link #getData()}.
+ */
+public final class PriorityDataProducer<T> extends BaseDataProducer<T> {
+    private final List<DataProducer<T>> mChildProducers;
+
+    public PriorityDataProducer(List<DataProducer<T>> childProducers) {
+        mChildProducers = childProducers;
+        for (DataProducer<T> childProducer : mChildProducers) {
+            childProducer.addDataChangedCallback(this::notifyDataChanged);
+        }
+    }
+
+    @Nullable
+    @Override
+    public Optional<T> getData() {
+        for (DataProducer<T> childProducer : mChildProducers) {
+            final Optional<T> data = childProducer.getData();
+            if (data.isPresent()) {
+                return data;
+            }
+        }
+        return Optional.empty();
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java
deleted file mode 100644
index 6dd190c..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.util;
-
-import static androidx.window.util.ExtensionHelper.isZero;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.R;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Device and display feature state provider that uses Settings as the source.
- */
-public final class SettingsConfigProvider extends ContentObserver {
-    private static final String TAG = "SettingsConfigProvider";
-    private static final String DEVICE_POSTURE = "device_posture";
-    private static final String DISPLAY_FEATURES = "display_features";
-
-    private static final Pattern FEATURE_PATTERN =
-            Pattern.compile("([a-z]+)-\\[(\\d+),(\\d+),(\\d+),(\\d+)]");
-
-    private static final String FEATURE_TYPE_FOLD = "fold";
-    private static final String FEATURE_TYPE_HINGE = "hinge";
-
-    private final Uri mDevicePostureUri =
-            Settings.Global.getUriFor(DEVICE_POSTURE);
-    private final Uri mDisplayFeaturesUri =
-            Settings.Global.getUriFor(DISPLAY_FEATURES);
-    private final Context mContext;
-    private final ContentResolver mResolver;
-    private final StateChangeCallback mCallback;
-    private boolean mRegisteredObservers;
-
-    public SettingsConfigProvider(@NonNull Context context, @NonNull StateChangeCallback callback) {
-        super(new Handler(Looper.getMainLooper()));
-        mContext = context;
-        mResolver = context.getContentResolver();
-        mCallback = callback;
-    }
-
-    /**
-     * Registers the content observers for Settings keys that store device state and display feature
-     * configurations.
-     */
-    public void registerObserversIfNeeded() {
-        if (mRegisteredObservers) {
-            return;
-        }
-        mRegisteredObservers = true;
-        mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendants */,
-                this /* ContentObserver */);
-        mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendants */,
-                this /* ContentObserver */);
-    }
-
-    /**
-     * Unregisters the content observers that are tracking the state changes.
-     * @see #registerObserversIfNeeded()
-     */
-    public void unregisterObserversIfNeeded() {
-        if (!mRegisteredObservers) {
-            return;
-        }
-        mRegisteredObservers = false;
-        mResolver.unregisterContentObserver(this);
-    }
-
-    /**
-     * Gets the device posture int stored in Settings.
-     */
-    public int getDeviceState() {
-        return Settings.Global.getInt(mResolver, DEVICE_POSTURE,
-                0 /* POSTURE_UNKNOWN */);
-    }
-
-    /**
-     * Gets the list of all display feature configs stored in Settings. Uses a custom
-     * {@link BaseDisplayFeature} class to report the config to be translated for actual
-     * containers in Sidecar or Extensions.
-     */
-    public List<BaseDisplayFeature> getDisplayFeatures() {
-        List<BaseDisplayFeature> features = new ArrayList<>();
-        String displayFeaturesString = Settings.Global.getString(mResolver, DISPLAY_FEATURES);
-        if (TextUtils.isEmpty(displayFeaturesString)) {
-            displayFeaturesString = mContext.getResources().getString(
-                    R.string.config_display_features);
-        }
-        if (TextUtils.isEmpty(displayFeaturesString)) {
-            return features;
-        }
-        String[] featureStrings =  displayFeaturesString.split(";");
-
-        int deviceState = getDeviceState();
-
-        for (String featureString : featureStrings) {
-            Matcher featureMatcher = FEATURE_PATTERN.matcher(featureString);
-            if (!featureMatcher.matches()) {
-                Log.e(TAG, "Malformed feature description format: " + featureString);
-                continue;
-            }
-            try {
-                String featureType = featureMatcher.group(1);
-                int type;
-                switch (featureType) {
-                    case FEATURE_TYPE_FOLD:
-                        type = 1 /* TYPE_FOLD */;
-                        break;
-                    case FEATURE_TYPE_HINGE:
-                        type = 2 /* TYPE_HINGE */;
-                        break;
-                    default: {
-                        Log.e(TAG, "Malformed feature type: " + featureType);
-                        continue;
-                    }
-                }
-
-                int left = Integer.parseInt(featureMatcher.group(2));
-                int top = Integer.parseInt(featureMatcher.group(3));
-                int right = Integer.parseInt(featureMatcher.group(4));
-                int bottom = Integer.parseInt(featureMatcher.group(5));
-                Rect featureRect = new Rect(left, top, right, bottom);
-                if (!isZero(featureRect)) {
-                    BaseDisplayFeature feature = new BaseDisplayFeature(type, deviceState,
-                            featureRect);
-                    features.add(feature);
-                } else {
-                    Log.w(TAG, "Read empty feature");
-                }
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "Malformed feature description: " + featureString);
-            }
-        }
-        return features;
-    }
-
-    @Override
-    public void onChange(boolean selfChange, Uri uri) {
-        if (uri == null) {
-            return;
-        }
-
-        if (mDevicePostureUri.equals(uri)) {
-            mCallback.onDevicePostureChanged();
-            mCallback.onDisplayFeaturesChanged();
-            return;
-        }
-        if (mDisplayFeaturesUri.equals(uri)) {
-            mCallback.onDisplayFeaturesChanged();
-        }
-    }
-
-    /**
-     * Callback that notifies about device or display feature state changes.
-     */
-    public interface StateChangeCallback {
-        /**
-         * Notifies about the device state update.
-         */
-        void onDevicePostureChanged();
-
-        /**
-         * Notifies about the display feature config update.
-         */
-        void onDisplayFeaturesChanged();
-    }
-}