Merge changes from topics "201676043", "201676597", "204605351"

* changes:
  Allow App Widget Overlays by Product Configuration.
  Add Support for App Widgets as Overlays.
  Centralized Dream Overlay State.
  SystemUI IDreamOverlay Implementation.
  Add Stub implementation for DreamOverlayService.
  Consolidate dream overlay binding logic.
  Pass Window to DreamOverlayService when attached.
  Correct launch task behind when dream is present.
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index d5ceafb..226deba 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -257,7 +257,8 @@
 
   public class DreamManager {
     method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isDreaming();
-    method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setActiveDream(@NonNull android.content.ComponentName);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setActiveDream(@Nullable android.content.ComponentName);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setDreamOverlay(@Nullable android.content.ComponentName);
     method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void startDream(@NonNull android.content.ComponentName);
     method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void stopDream();
   }
@@ -2364,6 +2365,17 @@
 
 }
 
+package android.service.dreams {
+
+  public abstract class DreamOverlayService extends android.app.Service {
+    ctor public DreamOverlayService();
+    method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public abstract void onStartDream(@NonNull android.view.WindowManager.LayoutParams);
+    method public final void requestExit();
+  }
+
+}
+
 package android.service.notification {
 
   @Deprecated public abstract class ConditionProviderService extends android.app.Service {
diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java
index f236813..34ae08fd 100644
--- a/core/java/android/app/DreamManager.java
+++ b/core/java/android/app/DreamManager.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
@@ -91,10 +92,30 @@
     @TestApi
     @UserHandleAware
     @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)
-    public void setActiveDream(@NonNull ComponentName dreamComponent) {
+    public void setActiveDream(@Nullable ComponentName dreamComponent) {
         ComponentName[] dreams = {dreamComponent};
+
         try {
-            mService.setDreamComponentsForUser(mContext.getUserId(), dreams);
+            mService.setDreamComponentsForUser(mContext.getUserId(),
+                    dreamComponent != null ? dreams : null);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the active dream on the device to be "dreamComponent".
+     *
+     * <p>This is only used for testing the dream service APIs.
+     *
+     * @hide
+     */
+    @TestApi
+    @UserHandleAware
+    @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)
+    public void setDreamOverlay(@Nullable ComponentName dreamOverlayComponent) {
+        try {
+            mService.registerDreamOverlayService(dreamOverlayComponent);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java
new file mode 100644
index 0000000..50f9d8a
--- /dev/null
+++ b/core/java/android/service/dreams/DreamOverlayService.java
@@ -0,0 +1,77 @@
+/*
+ * 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 android.service.dreams;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.WindowManager;
+
+
+/**
+ * Basic implementation of for {@link IDreamOverlay} for testing.
+ * @hide
+ */
+@TestApi
+public abstract class DreamOverlayService extends Service {
+    private static final String TAG = "DreamOverlayService";
+    private static final boolean DEBUG = false;
+
+    private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() {
+        @Override
+        public void startDream(WindowManager.LayoutParams layoutParams,
+                IDreamOverlayCallback callback) {
+            mDreamOverlayCallback = callback;
+            onStartDream(layoutParams);
+        }
+    };
+
+    IDreamOverlayCallback mDreamOverlayCallback;
+
+    public DreamOverlayService() {
+    }
+
+    @Nullable
+    @Override
+    public final IBinder onBind(@NonNull Intent intent) {
+        return mDreamOverlay.asBinder();
+    }
+
+    /**
+     * This method is overridden by implementations to handle when the dream has started and the
+     * window is ready to be interacted with.
+     * @param layoutParams The {@link android.view.WindowManager.LayoutParams} associated with the
+     *                     dream window.
+     */
+    public abstract void onStartDream(@NonNull WindowManager.LayoutParams layoutParams);
+
+    /**
+     * This method is invoked to request the dream exit.
+     */
+    public final void requestExit() {
+        try {
+            mDreamOverlayCallback.onExitRequested();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not request exit:" + e);
+        }
+    }
+}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 096595f..3ab6907 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -203,7 +203,6 @@
     private boolean mCanDoze;
     private boolean mDozing;
     private boolean mWindowless;
-    private boolean mOverlayServiceBound;
     private int mDozeScreenState = Display.STATE_UNKNOWN;
     private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
 
@@ -220,10 +219,34 @@
         // A Queue of pending requests to execute on the overlay.
         private ArrayDeque<Consumer<IDreamOverlay>> mRequests;
 
+        private boolean mBound;
+
         OverlayConnection() {
             mRequests = new ArrayDeque<>();
         }
 
+        public void bind(Context context, @Nullable ComponentName overlayService) {
+            if (overlayService == null) {
+                return;
+            }
+
+            final Intent overlayIntent = new Intent();
+            overlayIntent.setComponent(overlayService);
+
+            context.bindService(overlayIntent,
+                    this, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
+            mBound = true;
+        }
+
+        public void unbind(Context context) {
+            if (!mBound) {
+                return;
+            }
+
+            context.unbindService(this);
+            mBound = false;
+        }
+
         public void request(Consumer<IDreamOverlay> request) {
             mRequests.push(request);
             evaluate();
@@ -930,14 +953,8 @@
         mDreamServiceWrapper = new DreamServiceWrapper();
 
         // Connect to the overlay service if present.
-        final ComponentName overlayComponent =
-                intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT);
-        if (overlayComponent != null && !mWindowless) {
-            final Intent overlayIntent = new Intent();
-            overlayIntent.setComponent(overlayComponent);
-
-            mOverlayServiceBound = getApplicationContext().bindService(overlayIntent,
-                    mOverlayConnection, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
+        if (!mWindowless) {
+            mOverlayConnection.bind(this, intent.getParcelableExtra(EXTRA_DREAM_OVERLAY_COMPONENT));
         }
 
         return mDreamServiceWrapper;
@@ -973,10 +990,7 @@
             return;
         }
 
-        if (!mWindowless && mOverlayServiceBound) {
-            unbindService(mOverlayConnection);
-            mOverlayServiceBound = false;
-        }
+        mOverlayConnection.unbind(this);
 
         try {
             // finishSelf will unbind the dream controller from the dream service. This will
@@ -1173,6 +1187,16 @@
                     @Override
                     public void onViewAttachedToWindow(View v) {
                         mDispatchAfterOnAttachedToWindow.run();
+
+                        // Request the DreamOverlay be told to dream with dream's window parameters
+                        // once the window has been attached.
+                        mOverlayConnection.request(overlay -> {
+                            try {
+                                overlay.startDream(mWindow.getAttributes(), mOverlayCallback);
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "could not send window attributes:" + e);
+                            }
+                        });
                     }
 
                     @Override
@@ -1185,16 +1209,6 @@
                         }
                     }
                 });
-
-        // Request the DreamOverlay be told to dream with dream's window parameters once the service
-        // has connected.
-        mOverlayConnection.request(overlay -> {
-            try {
-                overlay.startDream(mWindow.getAttributes(), mOverlayCallback);
-            } catch (RemoteException e) {
-                Log.e(TAG, "could not send window attributes:" + e);
-            }
-        });
     }
 
     private boolean getWindowFlagValue(int flag, boolean defaultValue) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index c87ba65..26a4962 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -294,6 +294,11 @@
 
     <uses-permission android:name="android.permission.READ_PEOPLE_DATA" />
 
+    <!-- Permission for dream overlay. -->
+    <uses-permission android:name="android.permission.BIND_DREAM_SERVICE" />
+
+    <uses-permission android:name="android.permission.BIND_APPWIDGET" />
+
     <protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
     <protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
@@ -673,6 +678,11 @@
             android:name=".keyguard.KeyguardService"
             android:exported="true" />
 
+        <service
+            android:name=".dreams.DreamOverlayService"
+            android:enabled="@bool/config_dreamOverlayServiceEnabled"
+            android:exported="true" />
+
         <activity android:name=".keyguard.WorkLockActivity"
                   android:label="@string/accessibility_desc_work_lock"
                   android:permission="android.permission.MANAGE_USERS"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 9878e0d..96433e5 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -715,4 +715,22 @@
     <!-- Flag to enable privacy dot views, it shall be true for normal case -->
     <bool name="config_enablePrivacyDot">true</bool>
 
+    <!-- The positions widgets can be in defined as View.Gravity constants -->
+    <integer-array name="config_dreamOverlayPositions">
+    </integer-array>
+
+    <!-- Widget components to show as dream overlays -->
+    <string-array name="config_dreamOverlayComponents" translatable="false">
+    </string-array>
+
+    <!-- Width percentage of dream overlay components -->
+    <item name="config_dreamOverlayComponentWidthPercent" translatable="false" format="float"
+          type="dimen">0.33</item>
+
+    <!-- Height percentage of dream overlay components -->
+    <item name="config_dreamOverlayComponentHeightPercent" translatable="false" format="float"
+          type="dimen">0.25</item>
+
+    <!-- Flag to enable dream overlay service and its registration -->
+    <bool name="config_dreamOverlayServiceEnabled">false</bool>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index fe79110..33f07c7 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -21,6 +21,7 @@
 import com.android.systemui.ImageWallpaper;
 import com.android.systemui.SystemUIService;
 import com.android.systemui.doze.DozeService;
+import com.android.systemui.dreams.DreamOverlayService;
 import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
 import com.android.systemui.keyguard.KeyguardService;
 import com.android.systemui.screenrecord.RecordingService;
@@ -56,6 +57,12 @@
     /** */
     @Binds
     @IntoMap
+    @ClassKey(DreamOverlayService.class)
+    public abstract Service bindDreamOverlayService(DreamOverlayService service);
+
+    /** */
+    @Binds
+    @IntoMap
     @ClassKey(SystemUIService.class)
     public abstract Service bindSystemUIService(SystemUIService service);
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 30844cc..a5d4d80 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -23,6 +23,8 @@
 import com.android.systemui.accessibility.SystemActions;
 import com.android.systemui.accessibility.WindowMagnification;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.dreams.DreamOverlayRegistrant;
+import com.android.systemui.dreams.appwidgets.AppWidgetOverlayPrimer;
 import com.android.systemui.globalactions.GlobalActionsComponent;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
@@ -188,4 +190,18 @@
     @IntoMap
     @ClassKey(HomeSoundEffectController.class)
     public abstract SystemUI bindHomeSoundEffectController(HomeSoundEffectController sysui);
+
+    /** Inject into DreamOverlay. */
+    @Binds
+    @IntoMap
+    @ClassKey(DreamOverlayRegistrant.class)
+    public abstract SystemUI bindDreamOverlayRegistrant(
+            DreamOverlayRegistrant dreamOverlayRegistrant);
+
+    /** Inject into AppWidgetOverlayPrimer. */
+    @Binds
+    @IntoMap
+    @ClassKey(AppWidgetOverlayPrimer.class)
+    public abstract SystemUI bindAppWidgetOverlayPrimer(
+            AppWidgetOverlayPrimer appWidgetOverlayPrimer);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 799c92c..4cecb39 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -36,6 +36,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.demomode.dagger.DemoModeModule;
 import com.android.systemui.doze.dagger.DozeComponent;
+import com.android.systemui.dreams.dagger.DreamModule;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlagManager;
 import com.android.systemui.flags.FeatureFlags;
@@ -101,6 +102,7 @@
             AssistModule.class,
             ClockModule.class,
             CommunalModule.class,
+            DreamModule.class,
             ControlsModule.class,
             DemoModeModule.class,
             FalsingModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
new file mode 100644
index 0000000..20c46da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.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 com.android.systemui.dreams;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.PatternMatcher;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamManager;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import javax.inject.Inject;
+
+/**
+ * {@link DreamOverlayRegistrant} is responsible for telling system server that SystemUI should be
+ * the designated dream overlay component.
+ */
+public class DreamOverlayRegistrant extends SystemUI {
+    private static final String TAG = "DreamOverlayRegistrant";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private final IDreamManager mDreamManager;
+    private final ComponentName mOverlayServiceComponent;
+    private final Resources mResources;
+    private boolean mCurrentRegisteredState = false;
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) {
+                Log.d(TAG, "package changed receiver - onReceive");
+            }
+
+            registerOverlayService();
+        }
+    };
+
+    private void registerOverlayService() {
+        // Check to see if the service has been disabled by the user. In this case, we should not
+        // proceed modifying the enabled setting.
+        final PackageManager packageManager = mContext.getPackageManager();
+        final int enabledState =
+                packageManager.getComponentEnabledSetting(mOverlayServiceComponent);
+
+
+        // TODO(b/204626521): We should not have to set the component enabled setting if the
+        // enabled config flag is properly applied based on the RRO.
+        if (enabledState != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+            final int overlayState = mResources.getBoolean(R.bool.config_dreamOverlayServiceEnabled)
+                    ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                    : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+
+            if (overlayState != enabledState) {
+                packageManager
+                        .setComponentEnabledSetting(mOverlayServiceComponent, overlayState, 0);
+            }
+        }
+
+        // The overlay service is only registered when its component setting is enabled.
+        boolean register = packageManager.getComponentEnabledSetting(mOverlayServiceComponent)
+                == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+
+        if (mCurrentRegisteredState == register) {
+            return;
+        }
+
+        mCurrentRegisteredState = register;
+
+        try {
+            if (DEBUG) {
+                Log.d(TAG, mCurrentRegisteredState
+                        ? "registering dream overlay service:" + mOverlayServiceComponent
+                        : "clearing dream overlay service");
+            }
+
+            mDreamManager.registerDreamOverlayService(
+                    mCurrentRegisteredState ? mOverlayServiceComponent : null);
+        } catch (RemoteException e) {
+            Log.e(TAG, "could not register dream overlay service:" + e);
+        }
+    }
+
+    @Inject
+    public DreamOverlayRegistrant(Context context, @Main Resources resources) {
+        super(context);
+        mResources = resources;
+        mDreamManager = IDreamManager.Stub.asInterface(
+                ServiceManager.getService(DreamService.DREAM_SERVICE));
+        mOverlayServiceComponent = new ComponentName(mContext, DreamOverlayService.class);
+    }
+
+    @Override
+    public void start() {
+        final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addDataScheme("package");
+        filter.addDataSchemeSpecificPart(mOverlayServiceComponent.getPackageName(),
+                PatternMatcher.PATTERN_LITERAL);
+        // Note that we directly register the receiver here as data schemes are not supported by
+        // BroadcastDispatcher.
+        mContext.registerReceiver(mReceiver, filter);
+
+        registerOverlayService();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
new file mode 100644
index 0000000..8f0ea2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -0,0 +1,195 @@
+/*
+ * 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 com.android.systemui.dreams;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.ColorDrawable;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.PhoneWindow;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.concurrent.Executor;
+
+import javax.inject.Inject;
+
+/**
+ * The {@link DreamOverlayService} is responsible for placing overlays on top of a dream. The
+ * dream reaches directly out to the service with a Window reference (via LayoutParams), which the
+ * service uses to insert its own child Window into the dream's parent Window.
+ */
+public class DreamOverlayService extends android.service.dreams.DreamOverlayService {
+    private static final String TAG = "DreamOverlayService";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // The Context is used to construct the hosting constraint layout and child overlay views.
+    private final Context mContext;
+    // The Executor ensures actions and ui updates happen on the same thread.
+    private final Executor mExecutor;
+    // The state controller informs the service of updates to the overlays present.
+    private final DreamOverlayStateController mStateController;
+
+    // The window is populated once the dream informs the service it has begun dreaming.
+    private Window mWindow;
+    private ConstraintLayout mLayout;
+
+    private final DreamOverlayStateController.Callback mOverlayStateCallback =
+            new DreamOverlayStateController.Callback() {
+        @Override
+        public void onOverlayChanged() {
+            mExecutor.execute(() -> reloadOverlaysLocked());
+        }
+    };
+
+    // The service listens to view changes in order to declare that input occurring in areas outside
+    // the overlay should be passed through to the dream underneath.
+    private View.OnAttachStateChangeListener mRootViewAttachListener =
+            new View.OnAttachStateChangeListener() {
+        @Override
+        public void onViewAttachedToWindow(View v) {
+            v.getViewTreeObserver()
+                    .addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View v) {
+            v.getViewTreeObserver()
+                    .removeOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
+        }
+    };
+
+    // A hook into the internal inset calculation where we declare the overlays as the only
+    // touchable regions.
+    private ViewTreeObserver.OnComputeInternalInsetsListener mOnComputeInternalInsetsListener  =
+            new ViewTreeObserver.OnComputeInternalInsetsListener() {
+        @Override
+        public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+            if (mLayout != null) {
+                inoutInfo.setTouchableInsets(
+                        ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+                final Region region = new Region();
+                for (int i = 0; i < mLayout.getChildCount(); i++) {
+                    View child = mLayout.getChildAt(i);
+                    final Rect rect = new Rect();
+                    child.getGlobalVisibleRect(rect);
+                    region.op(rect, Region.Op.UNION);
+                }
+
+                inoutInfo.touchableRegion.set(region);
+            }
+        }
+    };
+
+    @Override
+    public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
+        mExecutor.execute(() -> addOverlayWindowLocked(layoutParams));
+    }
+
+    private void reloadOverlaysLocked() {
+        if (mLayout == null) {
+            return;
+        }
+        mLayout.removeAllViews();
+        for (OverlayProvider overlayProvider : mStateController.getOverlays()) {
+            addOverlay(overlayProvider);
+        }
+    }
+
+    /**
+     * Inserts {@link Window} to host dream overlays into the dream's parent window. Must be called
+     * from the main executing thread. The window attributes closely mirror those that are set by
+     * the {@link android.service.dreams.DreamService} on the dream Window.
+     * @param layoutParams The {@link android.view.WindowManager.LayoutParams} which allow inserting
+     *                     into the dream window.
+     */
+    private void addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) {
+        mWindow = new PhoneWindow(mContext);
+        mWindow.setAttributes(layoutParams);
+        mWindow.setWindowManager(null, layoutParams.token, "DreamOverlay", true);
+
+        mWindow.setBackgroundDrawable(new ColorDrawable(0));
+
+        mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+        mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
+        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+        // Hide all insets when the dream is showing
+        mWindow.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
+        mWindow.setDecorFitsSystemWindows(false);
+
+        if (DEBUG) {
+            Log.d(TAG, "adding overlay window to dream");
+        }
+
+        mLayout = new ConstraintLayout(mContext);
+        mLayout.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+        mLayout.addOnAttachStateChangeListener(mRootViewAttachListener);
+        mWindow.setContentView(mLayout);
+
+        final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+        windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
+        mExecutor.execute(this::reloadOverlaysLocked);
+    }
+
+    @VisibleForTesting
+    protected void addOverlay(OverlayProvider provider) {
+        provider.onCreateOverlay(mContext,
+                (view, layoutParams) -> {
+                    // Always move UI related work to the main thread.
+                    mExecutor.execute(() -> {
+                        if (mLayout == null) {
+                            return;
+                        }
+
+                        mLayout.addView(view, layoutParams);
+                    });
+                },
+                () -> {
+                    // The Callback is set on the main thread.
+                    mExecutor.execute(() -> {
+                        requestExit();
+                    });
+                });
+    }
+
+    @Inject
+    public DreamOverlayService(Context context, @Main Executor executor,
+            DreamOverlayStateController overlayStateController) {
+        mContext = context;
+        mExecutor = executor;
+        mStateController = overlayStateController;
+        mStateController.addCallback(mOverlayStateCallback);
+    }
+
+    @Override
+    public void onDestroy() {
+        mStateController.removeCallback(mOverlayStateCallback);
+        super.onDestroy();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
new file mode 100644
index 0000000..d248a9e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -0,0 +1,150 @@
+/*
+ * 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 com.android.systemui.dreams;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.policy.CallbackController;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Objects;
+
+import javax.inject.Inject;
+
+/**
+ * {@link DreamOverlayStateController} is the source of truth for Dream overlay configurations.
+ * Clients can register as listeners for changes to the overlay composition and can query for the
+ * overlays on-demand.
+ */
+@SysUISingleton
+public class DreamOverlayStateController implements
+        CallbackController<DreamOverlayStateController.Callback> {
+    // A counter for guaranteeing unique overlay tokens within the scope of this state controller.
+    private int mNextOverlayTokenId = 0;
+
+    /**
+     * {@link OverlayToken} provides a unique key for identifying {@link OverlayProvider}
+     * instances registered with {@link DreamOverlayStateController}.
+     */
+    public static class OverlayToken {
+        private final int mId;
+
+        private OverlayToken(int id) {
+            mId = id;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof OverlayToken)) return false;
+            OverlayToken that = (OverlayToken) o;
+            return mId == that.mId;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mId);
+        }
+    }
+
+    /**
+     * Callback for dream overlay events.
+     */
+    public interface Callback {
+        /**
+         * Called when the visibility of the communal view changes.
+         */
+        default void onOverlayChanged() {
+        }
+    }
+
+    private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+    private final HashMap<OverlayToken, OverlayProvider> mOverlays = new HashMap<>();
+
+    @VisibleForTesting
+    @Inject
+    public DreamOverlayStateController() {
+    }
+
+    /**
+     * Adds an overlay to be presented on top of dreams.
+     * @param provider The {@link OverlayProvider} providing the dream.
+     * @return The {@link OverlayToken} tied to the supplied {@link OverlayProvider}.
+     */
+    public OverlayToken addOverlay(OverlayProvider provider) {
+        final OverlayToken token = new OverlayToken(mNextOverlayTokenId++);
+        mOverlays.put(token, provider);
+        notifyCallbacks();
+        return token;
+    }
+
+    /**
+     * Removes an overlay from being shown on dreams.
+     * @param token The {@link OverlayToken} associated with the {@link OverlayProvider} to be
+     *              removed.
+     * @return The removed {@link OverlayProvider}, {@code null} if not found.
+     */
+    public OverlayProvider removeOverlay(OverlayToken token) {
+        final OverlayProvider removedOverlay = mOverlays.remove(token);
+
+        if (removedOverlay != null) {
+            notifyCallbacks();
+        }
+
+        return removedOverlay;
+    }
+
+    private void notifyCallbacks() {
+        for (Callback callback : mCallbacks) {
+            callback.onOverlayChanged();
+        }
+    }
+
+    @Override
+    public void addCallback(@NonNull Callback callback) {
+        Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
+        if (mCallbacks.contains(callback)) {
+            return;
+        }
+
+        mCallbacks.add(callback);
+
+        if (mOverlays.isEmpty()) {
+            return;
+        }
+
+        callback.onOverlayChanged();
+    }
+
+    @Override
+    public void removeCallback(@NonNull Callback callback) {
+        Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
+        mCallbacks.remove(callback);
+    }
+
+    /**
+     * Returns all registered {@link OverlayProvider} instances.
+     * @return A collection of {@link OverlayProvider}.
+     */
+    public Collection<OverlayProvider> getOverlays() {
+        return mOverlays.values();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/OverlayHost.java b/packages/SystemUI/src/com/android/systemui/dreams/OverlayHost.java
new file mode 100644
index 0000000..08f0f35
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/OverlayHost.java
@@ -0,0 +1,48 @@
+/*
+ * 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 com.android.systemui.dreams;
+
+import android.view.View;
+
+/**
+ * A collection of interfaces related to hosting an overlay.
+ */
+public abstract class OverlayHost {
+    /**
+     * An interface for the callback from the overlay provider to indicate when the overlay is
+     * ready.
+     */
+    public interface CreationCallback {
+        /**
+         * Called to inform the overlay view is ready to be placed within the visual space.
+         * @param view The view representing the overlay.
+         * @param layoutParams The parameters to create the view with.
+         */
+        void onCreated(View view, OverlayHostView.LayoutParams layoutParams);
+    }
+
+    /**
+     * An interface for the callback from the overlay provider to signal interactions in the
+     * overlay.
+     */
+    public interface InteractionCallback {
+        /**
+         * Called to signal the calling overlay would like to exit the dream.
+         */
+        void onExit();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/OverlayHostView.java b/packages/SystemUI/src/com/android/systemui/dreams/OverlayHostView.java
new file mode 100644
index 0000000..7870426
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/OverlayHostView.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 com.android.systemui.dreams;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+/**
+ * {@link OverlayHostView} is the container view for housing overlays ontop of a dream.
+ */
+public class OverlayHostView extends ConstraintLayout {
+    public OverlayHostView(Context context) {
+        super(context, null);
+    }
+
+    public OverlayHostView(Context context, AttributeSet attrs) {
+        super(context, attrs, 0);
+    }
+
+    public OverlayHostView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr, 0);
+    }
+
+    public OverlayHostView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/OverlayProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/OverlayProvider.java
new file mode 100644
index 0000000..f208025
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/OverlayProvider.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 com.android.systemui.dreams;
+
+import android.content.Context;
+
+/**
+ * {@link OverlayProvider} is an interface for defining entities that can supply overlays to show
+ * over a dream. Presentation components such as the {@link DreamOverlayService} supply
+ * implementations with the necessary context for constructing such overlays.
+ */
+public interface OverlayProvider {
+    /**
+     * Called when the {@link OverlayHost} requests the associated overlay be produced.
+     *
+     * @param context The {@link Context} used to construct the view.
+     * @param creationCallback The callback to inform when the overlay has been created.
+     * @param interactionCallback The callback to inform when the overlay has been interacted with.
+     */
+    void onCreateOverlay(Context context, OverlayHost.CreationCallback creationCallback,
+            OverlayHost.InteractionCallback interactionCallback);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayPrimer.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayPrimer.java
new file mode 100644
index 0000000..a0c7c29
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayPrimer.java
@@ -0,0 +1,119 @@
+/*
+ * 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 com.android.systemui.dreams.appwidgets;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.view.Gravity;
+
+import androidx.constraintlayout.widget.ConstraintSet;
+
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.OverlayHostView;
+import com.android.systemui.dreams.dagger.AppWidgetOverlayComponent;
+
+import javax.inject.Inject;
+
+/**
+ * {@link AppWidgetOverlayPrimer} reads the configured App Widget Overlay from resources on start
+ * and populates them into the {@link DreamOverlayStateController}.
+ */
+public class AppWidgetOverlayPrimer extends SystemUI {
+    private final Resources mResources;
+    private final DreamOverlayStateController mDreamOverlayStateController;
+    private final AppWidgetOverlayComponent.Factory mComponentFactory;
+
+    @Inject
+    public AppWidgetOverlayPrimer(Context context, @Main Resources resources,
+            DreamOverlayStateController overlayStateController,
+            AppWidgetOverlayComponent.Factory appWidgetOverlayFactory) {
+        super(context);
+        mResources = resources;
+        mDreamOverlayStateController = overlayStateController;
+        mComponentFactory = appWidgetOverlayFactory;
+    }
+
+    @Override
+    public void start() {
+    }
+
+    @Override
+    protected void onBootCompleted() {
+        super.onBootCompleted();
+        loadDefaultWidgets();
+    }
+
+    /**
+     * Generates the {@link OverlayHostView.LayoutParams} for a given gravity. Default dimension
+     * constraints are also included in the params.
+     * @param gravity The gravity for the layout as defined by {@link Gravity}.
+     * @param resources The resourcs from which default dimensions will be extracted from.
+     * @return {@link OverlayHostView.LayoutParams} representing the provided gravity and default
+     * parameters.
+     */
+    private static OverlayHostView.LayoutParams getLayoutParams(int gravity, Resources resources) {
+        final OverlayHostView.LayoutParams params = new OverlayHostView.LayoutParams(
+                OverlayHostView.LayoutParams.MATCH_CONSTRAINT,
+                OverlayHostView.LayoutParams.MATCH_CONSTRAINT);
+
+        if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
+            params.bottomToBottom = ConstraintSet.PARENT_ID;
+        }
+
+        if ((gravity & Gravity.TOP) == Gravity.TOP) {
+            params.topToTop = ConstraintSet.PARENT_ID;
+        }
+
+        if ((gravity & Gravity.END) == Gravity.END) {
+            params.endToEnd = ConstraintSet.PARENT_ID;
+        }
+
+        if ((gravity & Gravity.START) == Gravity.START) {
+            params.startToStart = ConstraintSet.PARENT_ID;
+        }
+
+        // For now, apply the same sizing constraints on every widget.
+        params.matchConstraintPercentHeight =
+                resources.getFloat(R.dimen.config_dreamOverlayComponentHeightPercent);
+        params.matchConstraintPercentWidth =
+                resources.getFloat(R.dimen.config_dreamOverlayComponentWidthPercent);
+
+        return params;
+    }
+
+
+    /**
+     * Helper method for loading widgets based on configuration.
+     */
+    private void loadDefaultWidgets() {
+        final int[] positions = mResources.getIntArray(R.array.config_dreamOverlayPositions);
+        final String[] components =
+                mResources.getStringArray(R.array.config_dreamOverlayComponents);
+
+        for (int i = 0; i < Math.min(positions.length, components.length); i++) {
+            final AppWidgetOverlayComponent component = mComponentFactory.build(
+                    ComponentName.unflattenFromString(components[i]),
+                    getLayoutParams(positions[i], mResources));
+
+            mDreamOverlayStateController.addOverlay(component.getAppWidgetOverlayProvider());
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayProvider.java
new file mode 100644
index 0000000..a635d3f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayProvider.java
@@ -0,0 +1,82 @@
+/*
+ * 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 com.android.systemui.dreams.appwidgets;
+
+import android.appwidget.AppWidgetHostView;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import com.android.systemui.dreams.OverlayHost;
+import com.android.systemui.dreams.OverlayHostView;
+import com.android.systemui.dreams.OverlayProvider;
+import com.android.systemui.plugins.ActivityStarter;
+
+import javax.inject.Inject;
+
+/**
+ * {@link AppWidgetOverlayProvider} is an implementation of {@link OverlayProvider} for providing
+ * app widget-based overlays.
+ */
+public class AppWidgetOverlayProvider implements OverlayProvider {
+    private static final String TAG = "AppWdgtOverlayProvider";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final ActivityStarter mActivityStarter;
+    private final AppWidgetProvider mAppWidgetProvider;
+    private final ComponentName mComponentName;
+    private final OverlayHostView.LayoutParams mLayoutParams;
+
+    @Inject
+    public AppWidgetOverlayProvider(ActivityStarter activityStarter,
+            ComponentName componentName, AppWidgetProvider widgetProvider,
+            OverlayHostView.LayoutParams layoutParams) {
+        mActivityStarter = activityStarter;
+        mComponentName = componentName;
+        mAppWidgetProvider = widgetProvider;
+        mLayoutParams = layoutParams;
+    }
+
+    @Override
+    public void onCreateOverlay(Context context, OverlayHost.CreationCallback creationCallback,
+            OverlayHost.InteractionCallback interactionCallback) {
+        final AppWidgetHostView widget = mAppWidgetProvider.getWidget(mComponentName);
+
+        if (widget == null) {
+            Log.e(TAG, "could not create widget");
+            return;
+        }
+
+        widget.setInteractionHandler((view, pendingIntent, response) -> {
+            if (pendingIntent.isActivity()) {
+                if (DEBUG) {
+                    Log.d(TAG, "launching pending intent from app widget:" + mComponentName);
+                }
+                interactionCallback.onExit();
+                mActivityStarter.startPendingIntentDismissingKeyguard(pendingIntent,
+                        null /*intentSentUiThreadCallback*/, view);
+                return true;
+            } else {
+                return RemoteViews.startPendingIntent(view, pendingIntent,
+                        response.getLaunchOptions(view));
+            }
+        });
+
+        creationCallback.onCreated(widget, mLayoutParams);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java
new file mode 100644
index 0000000..d1da1e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/appwidgets/AppWidgetProvider.java
@@ -0,0 +1,101 @@
+/*
+ * 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 com.android.systemui.dreams.appwidgets;
+
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.List;
+
+import javax.inject.Inject;
+
+/**
+ * {@link AppWidgetProvider} is a singleton for accessing app widgets within SystemUI. This
+ * consolidates resources such as the App Widget Host across potentially multiple
+ * {@link AppWidgetOverlayProvider} instances and other usages.
+ */
+@SysUISingleton
+public class AppWidgetProvider {
+    private static final String TAG = "AppWidgetProvider";
+    public static final int APP_WIDGET_HOST_ID = 1025;
+
+    private final Context mContext;
+    private final AppWidgetManager mAppWidgetManager;
+    private final AppWidgetHost mAppWidgetHost;
+    private final Resources mResources;
+
+    @Inject
+    public AppWidgetProvider(Context context, @Main Resources resources) {
+        mContext = context;
+        mResources = resources;
+        mAppWidgetManager = android.appwidget.AppWidgetManager.getInstance(context);
+        mAppWidgetHost = new AppWidgetHost(context, APP_WIDGET_HOST_ID);
+        mAppWidgetHost.startListening();
+    }
+
+    /**
+     * Returns an {@link AppWidgetHostView} associated with a given {@link ComponentName}.
+     * @param component The {@link ComponentName} of the target {@link AppWidgetHostView}.
+     * @return The {@link AppWidgetHostView} or {@code null} on error.
+     */
+    public AppWidgetHostView getWidget(ComponentName component) {
+        final List<AppWidgetProviderInfo> appWidgetInfos =
+                mAppWidgetManager.getInstalledProviders();
+
+        for (AppWidgetProviderInfo widgetInfo : appWidgetInfos) {
+            if (widgetInfo.provider.equals(component)) {
+                final int widgetId = mAppWidgetHost.allocateAppWidgetId();
+
+                boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(widgetId,
+                        widgetInfo.provider);
+
+                if (!success) {
+                    Log.e(TAG, "could not bind to app widget:" + component);
+                    break;
+                }
+
+                final AppWidgetHostView appWidgetView =
+                        mAppWidgetHost.createView(mContext, widgetId, widgetInfo);
+
+                if (appWidgetView != null) {
+                    // Register a layout change listener to update the widget on any sizing changes.
+                    appWidgetView.addOnLayoutChangeListener(
+                            (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+                                final float density = mResources.getDisplayMetrics().density;
+                                final int height = Math.round((bottom - top) / density);
+                                final int width = Math.round((right - left) / density);
+                                appWidgetView.updateAppWidgetSize(null, width, height, width,
+                                        height);
+                            });
+                }
+
+                return appWidgetView;
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/AppWidgetOverlayComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/AppWidgetOverlayComponent.java
new file mode 100644
index 0000000..3103057
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/AppWidgetOverlayComponent.java
@@ -0,0 +1,39 @@
+/*
+ * 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 com.android.systemui.dreams.dagger;
+
+import android.content.ComponentName;
+
+import com.android.systemui.dreams.OverlayHostView;
+import com.android.systemui.dreams.appwidgets.AppWidgetOverlayProvider;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/** */
+@Subcomponent
+public interface AppWidgetOverlayComponent {
+    /** */
+    @Subcomponent.Factory
+    interface Factory {
+        AppWidgetOverlayComponent build(@BindsInstance ComponentName component,
+                @BindsInstance OverlayHostView.LayoutParams layoutParams);
+    }
+
+    /** Builds a {@link AppWidgetOverlayProvider}. */
+    AppWidgetOverlayProvider getAppWidgetOverlayProvider();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
new file mode 100644
index 0000000..7bf2361
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -0,0 +1,28 @@
+/*
+ * 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 com.android.systemui.dreams.dagger;
+
+import dagger.Module;
+
+/**
+ * Dagger Module providing Communal-related functionality.
+ */
+@Module(subcomponents = {
+        AppWidgetOverlayComponent.class,
+})
+public interface DreamModule {
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/AppWidgetOverlayProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/AppWidgetOverlayProviderTest.java
new file mode 100644
index 0000000..0fc306b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/AppWidgetOverlayProviderTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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 com.android.systemui.dreams;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.isNull;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetHostView;
+import android.content.ComponentName;
+import android.testing.AndroidTestingRunner;
+import android.widget.RemoteViews;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.dreams.appwidgets.AppWidgetOverlayProvider;
+import com.android.systemui.dreams.appwidgets.AppWidgetProvider;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.utils.leaks.LeakCheckedTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class AppWidgetOverlayProviderTest extends SysuiTestCase {
+    @Mock
+    ActivityStarter mActivityStarter;
+
+    @Mock
+    ComponentName mComponentName;
+
+    @Mock
+    AppWidgetProvider mAppWidgetProvider;
+
+    @Mock
+    AppWidgetHostView mAppWidgetHostView;
+
+    @Mock
+    OverlayHost.CreationCallback mCreationCallback;
+
+    @Mock
+    OverlayHost.InteractionCallback mInteractionCallback;
+
+    @Mock
+    PendingIntent mPendingIntent;
+
+    @Mock
+    RemoteViews.RemoteResponse mRemoteResponse;
+
+    AppWidgetOverlayProvider mOverlayProvider;
+
+    RemoteViews.InteractionHandler mInteractionHandler;
+
+    @Rule
+    public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
+
+    @Rule
+    public SysuiTestableContext mContext = new SysuiTestableContext(
+            InstrumentationRegistry.getContext(), mLeakCheck);
+
+    OverlayHostView.LayoutParams mLayoutParams = new OverlayHostView.LayoutParams(
+            OverlayHostView.LayoutParams.MATCH_PARENT, OverlayHostView.LayoutParams.MATCH_PARENT);
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mPendingIntent.isActivity()).thenReturn(true);
+        when(mAppWidgetProvider.getWidget(mComponentName)).thenReturn(mAppWidgetHostView);
+
+        mOverlayProvider = new AppWidgetOverlayProvider(
+                mActivityStarter,
+                mComponentName,
+                mAppWidgetProvider,
+                mLayoutParams
+        );
+
+        final ArgumentCaptor<RemoteViews.InteractionHandler> creationCallbackCapture =
+                ArgumentCaptor.forClass(RemoteViews.InteractionHandler.class);
+
+        mOverlayProvider.onCreateOverlay(mContext, mCreationCallback, mInteractionCallback);
+        verify(mAppWidgetHostView, times(1))
+                .setInteractionHandler(creationCallbackCapture.capture());
+        mInteractionHandler = creationCallbackCapture.getValue();
+    }
+
+    @Test
+    public void testWidgetBringup() {
+        // Make sure widget was requested.
+        verify(mAppWidgetProvider, times(1)).getWidget(eq(mComponentName));
+
+        // Make sure widget was returned to callback.
+        verify(mCreationCallback, times(1)).onCreated(eq(mAppWidgetHostView),
+                eq(mLayoutParams));
+    }
+
+    @Test
+    public void testWidgetInteraction() {
+        // Trigger interaction.
+        mInteractionHandler.onInteraction(mAppWidgetHostView, mPendingIntent,
+                mRemoteResponse);
+
+        // Ensure activity is started.
+        verify(mActivityStarter, times(1))
+                .startPendingIntentDismissingKeyguard(eq(mPendingIntent), isNull(),
+                        eq(mAppWidgetHostView));
+        // Verify exit is requested.
+        verify(mInteractionCallback, times(1)).onExit();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
new file mode 100644
index 0000000..53bfeee
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -0,0 +1,153 @@
+/*
+ * 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 com.android.systemui.dreams;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.os.IBinder;
+import android.service.dreams.IDreamOverlay;
+import android.service.dreams.IDreamOverlayCallback;
+import android.testing.AndroidTestingRunner;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.WindowManagerImpl;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.utils.leaks.LeakCheckedTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamOverlayServiceTest extends SysuiTestCase {
+    private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+    private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
+
+    @Rule
+    public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
+
+    @Rule
+    public SysuiTestableContext mContext = new SysuiTestableContext(
+            InstrumentationRegistry.getContext(), mLeakCheck);
+
+    WindowManager.LayoutParams mWindowParams = new WindowManager.LayoutParams();
+
+    @Mock
+    IDreamOverlayCallback mDreamOverlayCallback;
+
+    @Mock
+    WindowManagerImpl mWindowManager;
+
+    @Mock
+    OverlayProvider mProvider;
+
+    @Mock
+    DreamOverlayStateController mDreamOverlayStateController;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mContext.addMockSystemService(WindowManager.class, mWindowManager);
+    }
+
+    @Test
+    public void testInteraction() throws Exception {
+        final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
+                mDreamOverlayStateController);
+        final IBinder proxy = service.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+        clearInvocations(mWindowManager);
+
+        // Inform the overlay service of dream starting.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        mMainExecutor.runAllReady();
+        verify(mWindowManager).addView(any(), any());
+
+        // Add overlay.
+        service.addOverlay(mProvider);
+        mMainExecutor.runAllReady();
+
+        final ArgumentCaptor<OverlayHost.CreationCallback> creationCallbackCapture =
+                ArgumentCaptor.forClass(OverlayHost.CreationCallback.class);
+        final ArgumentCaptor<OverlayHost.InteractionCallback> interactionCallbackCapture =
+                ArgumentCaptor.forClass(OverlayHost.InteractionCallback.class);
+
+        // Ensure overlay provider is asked to create view.
+        verify(mProvider).onCreateOverlay(any(), creationCallbackCapture.capture(),
+                interactionCallbackCapture.capture());
+        mMainExecutor.runAllReady();
+
+        // Inform service of overlay view creation.
+        final View view = new View(mContext);
+        creationCallbackCapture.getValue().onCreated(view, new ConstraintLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
+        ));
+
+        // Ask service to exit.
+        interactionCallbackCapture.getValue().onExit();
+        mMainExecutor.runAllReady();
+
+        // Ensure service informs dream host of exit.
+        verify(mDreamOverlayCallback).onExitRequested();
+    }
+
+    @Test
+    public void testListening() throws Exception {
+        final DreamOverlayService service = new DreamOverlayService(mContext, mMainExecutor,
+                mDreamOverlayStateController);
+
+        final IBinder proxy = service.onBind(new Intent());
+        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
+
+        // Inform the overlay service of dream starting.
+        overlay.startDream(mWindowParams, mDreamOverlayCallback);
+        mMainExecutor.runAllReady();
+
+        // Verify overlay service registered as listener with DreamOverlayStateController
+        // and inform callback of addition.
+        final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
+                ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+
+        verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
+        when(mDreamOverlayStateController.getOverlays()).thenReturn(Arrays.asList(mProvider));
+        callbackCapture.getValue().onOverlayChanged();
+        mMainExecutor.runAllReady();
+
+        // Verify provider is asked to create overlay.
+        verify(mProvider).onCreateOverlay(any(), any(), any());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
new file mode 100644
index 0000000..4e97be3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.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 com.android.systemui.dreams;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collection;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DreamOverlayStateControllerTest extends SysuiTestCase {
+    @Mock
+    DreamOverlayStateController.Callback mCallback;
+
+    @Mock
+    OverlayProvider mProvider;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testCallback() {
+        final DreamOverlayStateController stateController = new DreamOverlayStateController();
+        stateController.addCallback(mCallback);
+
+        // Add overlay and verify callback is notified.
+        final DreamOverlayStateController.OverlayToken token =
+                stateController.addOverlay(mProvider);
+
+        verify(mCallback, times(1)).onOverlayChanged();
+
+        final Collection<OverlayProvider> providers = stateController.getOverlays();
+        assertEquals(providers.size(), 1);
+        assertTrue(providers.contains(mProvider));
+
+        clearInvocations(mCallback);
+
+        // Remove overlay and verify callback is notified.
+        stateController.removeOverlay(token);
+        verify(mCallback, times(1)).onOverlayChanged();
+        assertTrue(providers.isEmpty());
+    }
+
+    @Test
+    public void testNotifyOnCallbackAdd() {
+        final DreamOverlayStateController stateController = new DreamOverlayStateController();
+        final DreamOverlayStateController.OverlayToken token =
+                stateController.addOverlay(mProvider);
+
+        // Verify callback occurs on add when an overlay is already present.
+        stateController.addCallback(mCallback);
+        verify(mCallback, times(1)).onOverlayChanged();
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayPrimerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayPrimerTest.java
new file mode 100644
index 0000000..2e5b165
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/appwidgets/AppWidgetOverlayPrimerTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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 com.android.systemui.dreams.appwidgets;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.view.Gravity;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.dagger.AppWidgetOverlayComponent;
+import com.android.systemui.utils.leaks.LeakCheckedTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class AppWidgetOverlayPrimerTest extends SysuiTestCase {
+    @Rule
+    public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck();
+
+    @Rule
+    public SysuiTestableContext mContext = new SysuiTestableContext(
+            InstrumentationRegistry.getContext(), mLeakCheck);
+
+    @Mock
+    Resources mResources;
+
+    @Mock
+    AppWidgetOverlayComponent mAppWidgetOverlayComponent1;
+    @Mock
+    AppWidgetOverlayComponent mAppWidgetOverlayComponent2;
+
+    @Mock
+    AppWidgetOverlayProvider mAppWidgetOverlayProvider1;
+
+    @Mock
+    AppWidgetOverlayProvider mAppWidgetOverlayProvider2;
+
+    final ComponentName mAppOverlayComponent1 =
+            ComponentName.unflattenFromString("com.foo.bar/.Baz");
+    final ComponentName mAppOverlayComponent2 =
+            ComponentName.unflattenFromString("com.foo.bar/.Baz2");
+
+    final int mAppOverlayGravity1 = Gravity.BOTTOM | Gravity.START;
+    final int mAppOverlayGravity2 = Gravity.BOTTOM | Gravity.END;
+
+    final String[] mComponents = new String[]{mAppOverlayComponent1.flattenToString(),
+            mAppOverlayComponent2.flattenToString() };
+    final int[] mPositions = new int[]{ mAppOverlayGravity1, mAppOverlayGravity2 };
+
+    @Mock
+    DreamOverlayStateController mDreamOverlayStateController;
+
+    @Mock
+    AppWidgetOverlayComponent.Factory mAppWidgetOverlayProviderFactory;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        when(mAppWidgetOverlayProviderFactory.build(eq(mAppOverlayComponent1), any()))
+                .thenReturn(mAppWidgetOverlayComponent1);
+        when(mAppWidgetOverlayComponent1.getAppWidgetOverlayProvider())
+                .thenReturn(mAppWidgetOverlayProvider1);
+        when(mAppWidgetOverlayProviderFactory.build(eq(mAppOverlayComponent2), any()))
+                .thenReturn(mAppWidgetOverlayComponent2);
+        when(mAppWidgetOverlayComponent2.getAppWidgetOverlayProvider())
+                .thenReturn(mAppWidgetOverlayProvider2);
+        when(mResources.getIntArray(R.array.config_dreamOverlayPositions)).thenReturn(mPositions);
+        when(mResources.getStringArray(R.array.config_dreamOverlayComponents))
+                .thenReturn(mComponents);
+    }
+
+    @Test
+    public void testLoading() {
+        final AppWidgetOverlayPrimer primer = new AppWidgetOverlayPrimer(mContext,
+                mResources,
+                mDreamOverlayStateController,
+                mAppWidgetOverlayProviderFactory);
+
+        // Inform primer to begin.
+        primer.onBootCompleted();
+
+        // Verify the first component is added to the state controller with the proper position.
+        {
+            final ArgumentCaptor<ConstraintLayout.LayoutParams> layoutParamsArgumentCaptor =
+                    ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class);
+            verify(mAppWidgetOverlayProviderFactory, times(1)).build(eq(mAppOverlayComponent1),
+                    layoutParamsArgumentCaptor.capture());
+
+            assertEquals(layoutParamsArgumentCaptor.getValue().startToStart,
+                    ConstraintLayout.LayoutParams.PARENT_ID);
+            assertEquals(layoutParamsArgumentCaptor.getValue().bottomToBottom,
+                    ConstraintLayout.LayoutParams.PARENT_ID);
+
+            verify(mDreamOverlayStateController, times(1))
+                    .addOverlay(eq(mAppWidgetOverlayProvider1));
+        }
+
+        // Verify the second component is added to the state controller with the proper position.
+        {
+            final ArgumentCaptor<ConstraintLayout.LayoutParams> layoutParamsArgumentCaptor =
+                    ArgumentCaptor.forClass(ConstraintLayout.LayoutParams.class);
+            verify(mAppWidgetOverlayProviderFactory, times(1)).build(eq(mAppOverlayComponent2),
+                    layoutParamsArgumentCaptor.capture());
+
+            assertEquals(layoutParamsArgumentCaptor.getValue().endToEnd,
+                    ConstraintLayout.LayoutParams.PARENT_ID);
+            assertEquals(layoutParamsArgumentCaptor.getValue().bottomToBottom,
+                    ConstraintLayout.LayoutParams.PARENT_ID);
+            verify(mDreamOverlayStateController, times(1))
+                    .addOverlay(eq(mAppWidgetOverlayProvider1));
+        }
+    }
+
+    @Test
+    public void testNoComponents() {
+        when(mResources.getStringArray(R.array.config_dreamOverlayComponents))
+                .thenReturn(new String[]{});
+
+        final AppWidgetOverlayPrimer primer = new AppWidgetOverlayPrimer(mContext,
+                mResources,
+                mDreamOverlayStateController,
+                mAppWidgetOverlayProviderFactory);
+
+        // Inform primer to begin.
+        primer.onBootCompleted();
+
+
+        // Make sure there is no request to add a widget if no components are specified by the
+        // product.
+        verify(mAppWidgetOverlayProviderFactory, never()).build(any(), any());
+        verify(mDreamOverlayStateController, never()).addOverlay(any());
+    }
+
+    @Test
+    public void testNoPositions() {
+        when(mResources.getIntArray(R.array.config_dreamOverlayPositions))
+                .thenReturn(new int[]{});
+
+        final AppWidgetOverlayPrimer primer = new AppWidgetOverlayPrimer(mContext,
+                mResources,
+                mDreamOverlayStateController,
+                mAppWidgetOverlayProviderFactory);
+
+        primer.onBootCompleted();
+
+        // Make sure there is no request to add a widget if no positions are specified by the
+        // product.
+        verify(mAppWidgetOverlayProviderFactory, never()).build(any(), any());
+        verify(mDreamOverlayStateController, never()).addOverlay(any());
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index b966ed1..00eb0d3 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -88,6 +88,7 @@
 import android.app.PendingIntent;
 import android.app.ProfilerInfo;
 import android.app.WaitResult;
+import android.app.WindowConfiguration;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
@@ -1745,6 +1746,16 @@
 
         mIntent.setFlags(mLaunchFlags);
 
+        boolean dreamStopping = false;
+
+        for (ActivityRecord stoppingActivity : mSupervisor.mStoppingActivities) {
+            if (stoppingActivity.getActivityType()
+                    == WindowConfiguration.ACTIVITY_TYPE_DREAM) {
+                dreamStopping = true;
+                break;
+            }
+        }
+
         // Get top task at beginning because the order may be changed when reusing existing task.
         final Task prevTopTask = mPreferredTaskDisplayArea.getFocusedRootTask();
         final Task reusedTask = getReusableTask();
@@ -1805,7 +1816,8 @@
 
         if (!mAvoidMoveToFront && mDoResume) {
             mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask);
-            if (!mTargetRootTask.isTopRootTaskInDisplayArea() && mService.mInternal.isDreaming()) {
+            if (!mTargetRootTask.isTopRootTaskInDisplayArea() && mService.mInternal.isDreaming()
+                    && !dreamStopping) {
                 // Launching underneath dream activity (fullscreen, always-on-top). Run the launch-
                 // -behind transition so the Activity gets created and starts in visible state.
                 mLaunchTaskBehind = true;