Add sample WM Jetpack Extensions impl

Moving some of the common bits of Sidecar and Extensions
into helper classes and adding both to the reference repo.

Bug: 154172225
Test: m androidx.window.extensions
Test: Manual, by including extensions in the emulator image
Test: gradlew window:window:cAT
Change-Id: I44ff06b06b2202a5d687216e017528098dc14d1d
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp
index 7fbbb61..4612ba2 100644
--- a/libs/WindowManager/Jetpack/Android.bp
+++ b/libs/WindowManager/Jetpack/Android.bp
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// Sidecar
 android_library_import {
     name: "window-sidecar",
     aars: ["window-sidecar-release.aar"],
@@ -20,7 +21,7 @@
 
 java_library {
     name: "androidx.window.sidecar",
-    srcs: ["src/**/*.java"],
+    srcs: ["src/androidx/window/sidecar/**/*.java", "src/androidx/window/util/**/*.java"],
     static_libs: ["window-sidecar"],
     installable: true,
     sdk_version: "core_platform",
@@ -36,3 +37,31 @@
     src: "androidx.window.sidecar.xml",
     filename_from_src: true,
 }
+
+// Extensions
+// NOTE: This module is still under active development and must not
+// be used in production. Use 'androidx.window.sidecar' instead.
+android_library_import {
+    name: "window-extensions",
+    aars: ["window-extensions-release.aar"],
+    sdk_version: "current",
+}
+
+java_library {
+    name: "androidx.window.extensions",
+    srcs: ["src/androidx/window/extensions/**/*.java", "src/androidx/window/util/**/*.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",],
+}
+
+prebuilt_etc {
+    name: "androidx.window.extensions.xml",
+    system_ext_specific: true,
+    sub_dir: "permissions",
+    src: "androidx.window.extensions.xml",
+    filename_from_src: true,
+}
diff --git a/libs/WindowManager/Jetpack/androidx.window.extensions.xml b/libs/WindowManager/Jetpack/androidx.window.extensions.xml
new file mode 100644
index 0000000..34264aa
--- /dev/null
+++ b/libs/WindowManager/Jetpack/androidx.window.extensions.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<permissions>
+    <library
+        name="androidx.window.extensions"
+        file="/system_ext/framework/androidx.window.extensions.jar"/>
+</permissions>
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java
new file mode 100644
index 0000000..b7a6039
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/ExtensionProvider.java
@@ -0,0 +1,41 @@
+/*
+ * 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.extensions;
+
+import android.content.Context;
+
+/**
+ * Provider class that will instantiate the library implementation. It must be included in the
+ * vendor library, and the vendor implementation must match the signature of this class.
+ */
+public class ExtensionProvider {
+    /**
+     * Provides a simple implementation of {@link ExtensionInterface} that can be replaced by
+     * an OEM by overriding this method.
+     */
+    public static ExtensionInterface getExtensionImpl(Context context) {
+        return new SampleExtensionImpl(context);
+    }
+
+    /**
+     * The support library will use this method to check API version compatibility.
+     * @return API version string in MAJOR.MINOR.PATCH-description format.
+     */
+    public static String getApiVersion() {
+        return "1.0.0-settings_sample";
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
new file mode 100644
index 0000000..0bf6965
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/SampleExtensionImpl.java
@@ -0,0 +1,109 @@
+/*
+ * 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.extensions;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
+import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.window.util.BaseDisplayFeature;
+import androidx.window.util.SettingsConfigProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reference implementation of androidx.window.extensions OEM interface for use with
+ * WindowManager Jetpack.
+ *
+ * NOTE: This version is a work in progress and under active development. It MUST NOT be used in
+ * 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 {
+    private static final String TAG = "SampleExtension";
+
+    private final SettingsConfigProvider mConfigProvider;
+
+    SampleExtensionImpl(Context context) {
+        mConfigProvider = new SettingsConfigProvider(context, this);
+    }
+
+    @Override
+    public void onDevicePostureChanged() {
+        updateDeviceState(new ExtensionDeviceState(mConfigProvider.getDeviceState()));
+    }
+
+    @Override
+    public void onDisplayFeaturesChanged() {
+        for (Activity activity : getActivitiesListeningForLayoutChanges()) {
+            ExtensionWindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
+            updateWindowLayout(activity, newLayout);
+        }
+    }
+
+    @NonNull
+    private ExtensionWindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
+        List<ExtensionDisplayFeature> displayFeatures = getDisplayFeatures(activity);
+        return new ExtensionWindowLayoutInfo(displayFeatures);
+    }
+
+    private List<ExtensionDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
+        List<ExtensionDisplayFeature> features = new ArrayList<>();
+        int displayId = activity.getDisplayId();
+        if (displayId != DEFAULT_DISPLAY) {
+            Log.w(TAG, "This sample doesn't support display features on secondary displays");
+            return features;
+        }
+
+        if (activity.isInMultiWindowMode()) {
+            // It is recommended not to report any display features in multi-window mode, since it
+            // won't be possible to synchronize the display feature positions with window movement.
+            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()));
+        }
+        return features;
+    }
+
+    @Override
+    protected void onListenersChanged() {
+        if (hasListeners()) {
+            mConfigProvider.registerObserversIfNeeded();
+        } else {
+            mConfigProvider.unregisterObserversIfNeeded();
+        }
+
+        onDevicePostureChanged();
+        onDisplayFeaturesChanged();
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java
new file mode 100644
index 0000000..b0895ef
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/StubExtension.java
@@ -0,0 +1,86 @@
+/*
+ * 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.extensions;
+
+import android.app.Activity;
+
+import androidx.annotation.NonNull;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Basic implementation of the {@link ExtensionInterface}. An OEM can choose to use it as the base
+ * class for their implementation.
+ */
+abstract class StubExtension implements ExtensionInterface {
+
+    private ExtensionCallback mExtensionCallback;
+    private final Set<Activity> mWindowLayoutChangeListenerActivities = new HashSet<>();
+    private boolean mDeviceStateChangeListenerRegistered;
+
+    StubExtension() {
+    }
+
+    @Override
+    public void setExtensionCallback(@NonNull ExtensionCallback extensionCallback) {
+        this.mExtensionCallback = extensionCallback;
+    }
+
+    @Override
+    public void onWindowLayoutChangeListenerAdded(@NonNull Activity activity) {
+        this.mWindowLayoutChangeListenerActivities.add(activity);
+        this.onListenersChanged();
+    }
+
+    @Override
+    public void onWindowLayoutChangeListenerRemoved(@NonNull Activity activity) {
+        this.mWindowLayoutChangeListenerActivities.remove(activity);
+        this.onListenersChanged();
+    }
+
+    @Override
+    public void onDeviceStateListenersChanged(boolean isEmpty) {
+        this.mDeviceStateChangeListenerRegistered = !isEmpty;
+        this.onListenersChanged();
+    }
+
+    void updateDeviceState(ExtensionDeviceState newState) {
+        if (this.mExtensionCallback != null) {
+            mExtensionCallback.onDeviceStateChanged(newState);
+        }
+    }
+
+    void updateWindowLayout(@NonNull Activity activity,
+            @NonNull ExtensionWindowLayoutInfo newLayout) {
+        if (this.mExtensionCallback != null) {
+            mExtensionCallback.onWindowLayoutChanged(activity, newLayout);
+        }
+    }
+
+    @NonNull
+    Set<Activity> getActivitiesListeningForLayoutChanges() {
+        return mWindowLayoutChangeListenerActivities;
+    }
+
+    protected boolean hasListeners() {
+        return !mWindowLayoutChangeListenerActivities.isEmpty()
+                || mDeviceStateChangeListenerRegistered;
+    }
+
+    protected abstract void onListenersChanged();
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
new file mode 100644
index 0000000..1094a0e
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.sidecar;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.window.util.ExtensionHelper.rotateRectToDisplayRotation;
+import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
+
+import android.app.Activity;
+import android.app.ActivityThread;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.window.util.BaseDisplayFeature;
+import androidx.window.util.SettingsConfigProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Reference implementation of androidx.window.sidecar OEM interface for use with
+ * WindowManager Jetpack.
+ */
+class SampleSidecarImpl extends StubSidecar implements
+        SettingsConfigProvider.StateChangeCallback {
+    private static final String TAG = "SampleSidecar";
+
+    private final SettingsConfigProvider mConfigProvider;
+
+    SampleSidecarImpl(Context context) {
+        mConfigProvider = new SettingsConfigProvider(context, this);
+    }
+
+    @Override
+    public void onDevicePostureChanged() {
+        updateDeviceState(getDeviceState());
+    }
+
+    @Override
+    public void onDisplayFeaturesChanged() {
+        for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
+            SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
+            updateWindowLayout(windowToken, newLayout);
+        }
+    }
+
+    @NonNull
+    @Override
+    public SidecarDeviceState getDeviceState() {
+        SidecarDeviceState deviceState = new SidecarDeviceState();
+        deviceState.posture = mConfigProvider.getDeviceState();
+        return deviceState;
+    }
+
+    @NonNull
+    @Override
+    public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
+        Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken);
+        SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo();
+        if (activity == null) {
+            return windowLayoutInfo;
+        }
+        windowLayoutInfo.displayFeatures = getDisplayFeatures(activity);
+        return windowLayoutInfo;
+    }
+
+    private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
+        List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>();
+        int displayId = activity.getDisplayId();
+        if (displayId != DEFAULT_DISPLAY) {
+            Log.w(TAG, "This sample doesn't support display features on secondary displays");
+            return features;
+        }
+
+        if (activity.isInMultiWindowMode()) {
+            // It is recommended not to report any display features in multi-window mode, since it
+            // won't be possible to synchronize the display feature positions with window movement.
+            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);
+        }
+        return features;
+    }
+
+    @Override
+    protected void onListenersChanged() {
+        if (hasListeners()) {
+            mConfigProvider.registerObserversIfNeeded();
+        } else {
+            mConfigProvider.unregisterObserversIfNeeded();
+        }
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java
deleted file mode 100644
index 5397302..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SettingsSidecarImpl.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.sidecar;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static androidx.window.sidecar.SidecarHelper.getWindowDisplay;
-import static androidx.window.sidecar.SidecarHelper.isInMultiWindow;
-import static androidx.window.sidecar.SidecarHelper.rotateRectToDisplayRotation;
-import static androidx.window.sidecar.SidecarHelper.transformToWindowSpaceRect;
-
-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.IBinder;
-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;
-
-class SettingsSidecarImpl extends StubSidecar {
-    private static final String TAG = "SettingsSidecar";
-
-    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 Context mContext;
-    private SettingsObserver mSettingsObserver;
-
-    final class SettingsObserver extends ContentObserver {
-        private final Uri mDevicePostureUri =
-                Settings.Global.getUriFor(DEVICE_POSTURE);
-        private final Uri mDisplayFeaturesUri =
-                Settings.Global.getUriFor(DISPLAY_FEATURES);
-        private final ContentResolver mResolver = mContext.getContentResolver();
-        private boolean mRegisteredObservers;
-
-
-        private SettingsObserver() {
-            super(new Handler(Looper.getMainLooper()));
-        }
-
-        private void registerObserversIfNeeded() {
-            if (mRegisteredObservers) {
-                return;
-            }
-            mRegisteredObservers = true;
-            mResolver.registerContentObserver(mDevicePostureUri, false /* notifyForDescendents */,
-                    this /* ContentObserver */);
-            mResolver.registerContentObserver(mDisplayFeaturesUri, false /* notifyForDescendents */,
-                    this /* ContentObserver */);
-        }
-
-        private void unregisterObserversIfNeeded() {
-            if (!mRegisteredObservers) {
-                return;
-            }
-            mRegisteredObservers = false;
-            mResolver.unregisterContentObserver(this);
-        }
-
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            if (uri == null) {
-                return;
-            }
-
-            if (mDevicePostureUri.equals(uri)) {
-                updateDevicePosture();
-                return;
-            }
-            if (mDisplayFeaturesUri.equals(uri)) {
-                updateDisplayFeatures();
-                return;
-            }
-        }
-    }
-
-    SettingsSidecarImpl(Context context) {
-        mContext = context;
-        mSettingsObserver = new SettingsObserver();
-    }
-
-    private void updateDevicePosture() {
-        updateDeviceState(getDeviceState());
-    }
-
-    /** Update display features with values read from settings. */
-    private void updateDisplayFeatures() {
-        for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
-            SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
-            updateWindowLayout(windowToken, newLayout);
-        }
-    }
-
-    @NonNull
-    @Override
-    public SidecarDeviceState getDeviceState() {
-        ContentResolver resolver = mContext.getContentResolver();
-        int posture = Settings.Global.getInt(resolver, DEVICE_POSTURE,
-                SidecarDeviceState.POSTURE_UNKNOWN);
-        SidecarDeviceState deviceState = new SidecarDeviceState();
-        deviceState.posture = posture;
-        return deviceState;
-    }
-
-    @NonNull
-    @Override
-    public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
-        List<SidecarDisplayFeature> displayFeatures = readDisplayFeatures(windowToken);
-        SidecarWindowLayoutInfo windowLayoutInfo = new SidecarWindowLayoutInfo();
-        windowLayoutInfo.displayFeatures = displayFeatures;
-        return windowLayoutInfo;
-    }
-
-    private List<SidecarDisplayFeature> readDisplayFeatures(IBinder windowToken) {
-        List<SidecarDisplayFeature> features = new ArrayList<SidecarDisplayFeature>();
-        int displayId = getWindowDisplay(windowToken);
-        if (displayId != DEFAULT_DISPLAY) {
-            Log.w(TAG, "This sample doesn't support display features on secondary displays");
-            return features;
-        }
-
-        if (isInMultiWindow(windowToken)) {
-            // It is recommended not to report any display features in multi-window mode, since it
-            // won't be possible to synchronize the display feature positions with window movement.
-            return features;
-        }
-
-        ContentResolver resolver = mContext.getContentResolver();
-        String displayFeaturesString = Settings.Global.getString(resolver, 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(";");
-        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 = SidecarDisplayFeature.TYPE_FOLD;
-                        break;
-                    case FEATURE_TYPE_HINGE:
-                        type = SidecarDisplayFeature.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);
-                rotateRectToDisplayRotation(featureRect, displayId);
-                transformToWindowSpaceRect(featureRect, windowToken);
-                if (isNotZero(featureRect)) {
-                    SidecarDisplayFeature feature = new SidecarDisplayFeature();
-                    feature.setRect(featureRect);
-                    feature.setType(type);
-                    features.add(feature);
-                } else {
-                    Log.w(TAG, "Failed to adjust feature to window");
-                }
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "Malformed feature description: " + featureString);
-            }
-        }
-        return features;
-    }
-
-    private static boolean isNotZero(Rect rect) {
-        return rect.height() > 0 || rect.width() > 0;
-    }
-
-    @Override
-    protected void onListenersChanged() {
-        if (mSettingsObserver == null) {
-            return;
-        }
-
-        if (hasListeners()) {
-            mSettingsObserver.registerObserversIfNeeded();
-        } else {
-            mSettingsObserver.unregisterObserversIfNeeded();
-        }
-    }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
index 0b4915ed..e6f8388 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
@@ -28,7 +28,7 @@
      * an OEM by overriding this method.
      */
     public static SidecarInterface getSidecarImpl(Context context) {
-        return new SettingsSidecarImpl(context);
+        return new SampleSidecarImpl(context);
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.java
new file mode 100644
index 0000000..b74a2a4
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDisplayFeature.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 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/sidecar/SidecarHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
similarity index 62%
rename from libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
rename to libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
index e5b6cff..2a593f1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * 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.
@@ -14,30 +14,36 @@
  * limitations under the License.
  */
 
-package androidx.window.sidecar;
+package androidx.window.util;
 
-import static android.view.Display.INVALID_DISPLAY;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
 import android.app.Activity;
-import android.app.ActivityThread;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerGlobal;
-import android.os.IBinder;
 import android.view.DisplayInfo;
 import android.view.Surface;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-class SidecarHelper {
+/**
+ * Util class for both Sidecar and Extensions.
+ */
+public final class ExtensionHelper {
+
+    private ExtensionHelper() {
+        // Util class, no instances should be created.
+    }
+
     /**
-     * Rotate the input rectangle specified in default display orientation to the current display
+     * Rotates the input rectangle specified in default display orientation to the current display
      * rotation.
      */
-    static void rotateRectToDisplayRotation(Rect inOutRect, int displayId) {
+    public static void rotateRectToDisplayRotation(int displayId, Rect inOutRect) {
         DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance();
         DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId);
         int rotation = displayInfo.rotation;
@@ -52,7 +58,7 @@
     }
 
     /**
-     * Rotate the input rectangle within parent bounds for a given delta.
+     * Rotates the input rectangle within parent bounds for a given delta.
      */
     private static void rotateBounds(Rect inOutRect, int parentWidth, int parentHeight,
             @Surface.Rotation int delta) {
@@ -79,9 +85,9 @@
         }
     }
 
-    /** Transform rectangle from absolute coordinate space to the window coordinate space. */
-    static void transformToWindowSpaceRect(Rect inOutRect, IBinder windowToken) {
-        Rect windowRect = getWindowBounds(windowToken);
+    /** Transforms rectangle from absolute coordinate space to the window coordinate space. */
+    public static void transformToWindowSpaceRect(Activity activity, Rect inOutRect) {
+        Rect windowRect = getWindowBounds(activity);
         if (windowRect == null) {
             inOutRect.setEmpty();
             return;
@@ -95,32 +101,17 @@
     }
 
     /**
-     * Get the current window bounds in absolute coordinates.
-     * NOTE: Only works with Activity windows.
+     * Gets the current window bounds in absolute coordinates.
      */
     @Nullable
-    private static Rect getWindowBounds(IBinder windowToken) {
-        Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken);
-        return activity != null
-                ? activity.getWindowManager().getCurrentWindowMetrics().getBounds()
-                : null;
+    private static Rect getWindowBounds(@NonNull Activity activity) {
+        return activity.getWindowManager().getCurrentWindowMetrics().getBounds();
     }
 
     /**
-     * Check if this window is an Activity window that is in multi-window mode.
+     * Checks if both dimensions of the given rect are zero at the same time.
      */
-    static boolean isInMultiWindow(IBinder windowToken) {
-        Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken);
-        return activity != null && activity.isInMultiWindowMode();
-    }
-
-    /**
-     * Get the id of the parent display for the window.
-     * NOTE: Only works with Activity windows.
-     */
-    static int getWindowDisplay(IBinder windowToken) {
-        Activity activity = ActivityThread.currentActivityThread().getActivity(windowToken);
-        return activity != null
-                ? activity.getWindowManager().getDefaultDisplay().getDisplayId() : INVALID_DISPLAY;
+    public static boolean isZero(@NonNull Rect rect) {
+        return rect.height() == 0 && rect.width() == 0;
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java
new file mode 100644
index 0000000..6dd190c
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/SettingsConfigProvider.java
@@ -0,0 +1,196 @@
+/*
+ * 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();
+    }
+}
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
new file mode 100644
index 0000000..7b306b0
--- /dev/null
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ