diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index c87ba657bf466..25b73ab401a8a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -673,6 +673,11 @@
android:name=".keyguard.KeyguardService"
android:exported="true" />
+
+
true
+
+ false
+
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index fe7911045dfcb..33f07c716f958 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 android.app.Service;
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;
@@ -53,6 +54,12 @@ public abstract class DefaultServiceBinder {
@ClassKey(KeyguardService.class)
public abstract Service bindKeyguardService(KeyguardService service);
+ /** */
+ @Binds
+ @IntoMap
+ @ClassKey(DreamOverlayService.class)
+ public abstract Service bindDreamOverlayService(DreamOverlayService service);
+
/** */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 30844ccc877b5..11bee7ed0669c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -23,6 +23,7 @@ import com.android.systemui.SystemUI;
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.globalactions.GlobalActionsComponent;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.dagger.KeyguardModule;
@@ -188,4 +189,11 @@ public abstract class SystemUIBinder {
@IntoMap
@ClassKey(HomeSoundEffectController.class)
public abstract SystemUI bindHomeSoundEffectController(HomeSoundEffectController sysui);
+
+ /** Inject into DreamOverlay. */
+ @Binds
+ @IntoMap
+ @ClassKey(DreamOverlayRegistrant.class)
+ public abstract SystemUI bindDreamOverlayRegistrant(
+ DreamOverlayRegistrant dreamOverlayRegistrant);
}
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 0000000000000..20c46da14e637
--- /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 0000000000000..d37914a63a390
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -0,0 +1,165 @@
+/*
+ * 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 window is populated once the dream informs the service it has begun dreaming.
+ private Window mWindow;
+ private ConstraintLayout mLayout;
+
+ // 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));
+ }
+
+ /**
+ * 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());
+ }
+
+ @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) {
+ mContext = context;
+ mExecutor = executor;
+ }
+}
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 0000000000000..08f0f3507e3e0
--- /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 0000000000000..7870426c78f1c
--- /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 0000000000000..f20802527d739
--- /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/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
new file mode 100644
index 0000000000000..6223048f0ce5e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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 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;
+
+@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;
+
+ @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);
+ 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 creationCallbackCapture =
+ ArgumentCaptor.forClass(OverlayHost.CreationCallback.class);
+ final ArgumentCaptor 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();
+ }
+}