From 92fdf1d25bad9833995f119f2d0bf4e65c4c995e Mon Sep 17 00:00:00 2001 From: Bryce Lee Date: Thu, 30 Sep 2021 16:24:27 -0700 Subject: [PATCH] SystemUI IDreamOverlay Implementation. This changelist implements IDreamOverlay in the form of the DreamOverlayService. DreamOverlayRegistrant is responsible for registering this service with the system as the dream overlay component. This changelist also introduces a series of interfaces, such as OverlayProvider, which define the entities DreamOverlayService interacts with the generate overlay content. Bug: 201676597 Test: atest DreamOverlayServiceTest Change-Id: Idb14172672642580fdfa042449b6cad4bf68969b --- packages/SystemUI/AndroidManifest.xml | 5 + packages/SystemUI/res/values/config.xml | 3 + .../systemui/dagger/DefaultServiceBinder.java | 7 + .../systemui/dagger/SystemUIBinder.java | 8 + .../dreams/DreamOverlayRegistrant.java | 128 ++++++++++++++ .../systemui/dreams/DreamOverlayService.java | 165 ++++++++++++++++++ .../android/systemui/dreams/OverlayHost.java | 48 +++++ .../systemui/dreams/OverlayHostView.java | 43 +++++ .../systemui/dreams/OverlayProvider.java | 36 ++++ .../dreams/DreamOverlayServiceTest.java | 120 +++++++++++++ 10 files changed, 563 insertions(+) create mode 100644 packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java create mode 100644 packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java create mode 100644 packages/SystemUI/src/com/android/systemui/dreams/OverlayHost.java create mode 100644 packages/SystemUI/src/com/android/systemui/dreams/OverlayHostView.java create mode 100644 packages/SystemUI/src/com/android/systemui/dreams/OverlayProvider.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java 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(); + } +}